应用小程序验证工具包
<!-- 微信小程序API,可以实现微信登录,微信支付等功能--> <dependency> <groupId>com.github.binarywang</groupId> <artifactId>weixin-java-miniapp</artifactId> <version>3.5.0</version> </dependency>
新建配置类WxConfiguration
package com.itmuch.usercenter.configuration; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.config.WxMaConfig; import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.Bean; @Configurable public class WxConfiguration { @Bean public WxMaConfig wxMaConfig(){ WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); config.setAppid("wx48ecafb715371267"); config.setSecret("1644c28011c189f6186a1bc4f9205b6b"); return config; } @Bean public WxMaService wxMaService(WxMaConfig wxMaConfig){ WxMaServiceImpl service = new WxMaServiceImpl(); service.setWxMaConfig(wxMaConfig); return service; } }
在控制器中就可以使用了
package com.itmuch.usercenter.controller.user; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import com.alibaba.csp.ahas.shaded.com.alibaba.acm.shaded.com.google.common.collect.Maps; import com.itmuch.usercenter.auth.CheckLogin; import com.itmuch.usercenter.domain.dto.user.JwtTokenRespDto; import com.itmuch.usercenter.domain.dto.user.LoginRespDto; import com.itmuch.usercenter.domain.dto.user.UserLoginDto; import com.itmuch.usercenter.domain.dto.user.UserRespDto; import com.itmuch.usercenter.domain.entity.user.User; import com.itmuch.usercenter.service.user.UserService; import com.itmuch.usercenter.util.JwtOperator; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/users") @Slf4j public class UserController { @Autowired private UserService userService; @Resource private WxMaService wxMaService; @Autowired private JwtOperator jwtOperatorl; @GetMapping("/{id}") @CheckLogin public User findById(@PathVariable Integer id) { log.info("我被请求了"); return this.userService.findUserById(id); } /** * 模拟生成token * @return */ @GetMapping("/gen-token") public String genToken(){ HashMap<String, Object> objectObjectHashMap = Maps.newHashMap(); objectObjectHashMap.put("id", "1"); objectObjectHashMap.put("wxNickname", "fox"); objectObjectHashMap.put("role", "user"); return this.jwtOperatorl.generateToken(objectObjectHashMap); } @PostMapping("/login") public LoginRespDto login(@RequestBody UserLoginDto userLoginDto) throws WxErrorException { //微信小程序登陆的结果 WxMaJscode2SessionResult result = this.wxMaService.getUserService().getSessionInfo(userLoginDto.getCode()); //微信的openId,用户在微信上的唯一标识 String openId = result.getOpenid(); //查看用户是否已经注册,如果没有注册就注册(插入user表) //如果已经注册 User user = this.userService.login(userLoginDto, openId); //就颁发token Map<String, Object> userInfo = new HashMap<>(); userInfo.put("id", user.getId()); userInfo.put("wxNickName", user.getWxNickname()); userInfo.put("role", user.getRoles()); String token = jwtOperatorl.generateToken(userInfo); log.info( "用户{}登录成功,生成token = {} ,有效期为{}", userLoginDto.getWxNickname(), token, jwtOperatorl.getExpirationTime() ); //构建响应 return LoginRespDto.builder() .userRespDto( UserRespDto.builder() .id(user.getId()) .avatarUrl(user.getAvatarUrl()) .bonus(user.getBonus()) .wxNickname(user.getWxNickname()) .build() ) .jwtTokenRespDto( JwtTokenRespDto.builder() .expirationTime(jwtOperatorl.getExpirationTime().getTime()) .token(token) .build() ) .build(); } }
在findById加上自定义注解@checklogin
@checklogin利用切面进行设置
先定义一个注解
package com.itmuch.usercenter.auth; public @interface CheckLogin { }
@checklogin利用切面进行设置
package com.itmuch.usercenter.auth; import com.itmuch.usercenter.util.JwtOperator; import io.jsonwebtoken.Claims; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; @Aspect @Component public class CheckLoginAspect { @Resource private JwtOperator jwtOperator; @Around("@annotation(com.itmuch.usercenter.auth.CheckLogin)")//只要加了checklogin注解的都会走这个方法 public Object checkLogin(ProceedingJoinPoint point) { try { //从header中获取token //利用静态方法获取request RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; HttpServletRequest request = attributes.getRequest(); String token = request.getHeader("X-Token"); //检查token是否合法,是否过期,如果不通过直接抛异常,否则放行 Boolean isVaild = jwtOperator.validateToken(token); if (!isVaild){ throw new SecurityException("token不合法或过期"); } //检查通过,将用户信息放进request的attribute中 Claims claims = jwtOperator.getClaimsFromToken(token); request.setAttribute("id",claims.get("id")); request.setAttribute("wxNickname",claims.get("wxNickname")); request.setAttribute("role",claims.get("role")); //放行 return point.proceed(); }catch (Throwable throwable){ throw new SecurityException("token不合法或过期"); } } }
设置自定义异常
package com.itmuch.usercenter.security; public class SecurityException extends RuntimeException{ }
设置自定义异常捕捉器
package com.itmuch.usercenter.auth; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; //统一异常捕捉器 @RestControllerAdvice @Slf4j public class GlobalExceptionErrorHandler { @ExceptionHandler(SecurityException.class) public ResponseEntity<ErrorBody> error(SecurityException e) { log.warn("发生securityException异常",e); ResponseEntity<ErrorBody> errorBodyResponseEntity = new ResponseEntity<>( ErrorBody.builder() .body("Token非法,用户不允许访问") .status(HttpStatus.UNAUTHORIZED.value()) .build(), HttpStatus.UNAUTHORIZED ); return errorBodyResponseEntity; } } @Data @Builder @AllArgsConstructor @NoArgsConstructor class ErrorBody { private Integer status; private String body; }
总结:在访问192.168.0.100:8003/users/1的时候需要携带X-Token,不然会报错
同样将文件复制到内容中心,在/share/{id}接口上打上
@CheckLogin
注解,等携带X-Token访问时会报错,是因为内容中心的token没有传递到用户中心,一下是服务之间传递token的方式:
feign传递token(@RequestHeader)
package com.itmuch.content.controller.content; import com.itmuch.content.auth.CheckLogin; import com.itmuch.content.domain.dto.content.ShareDTO; import com.itmuch.content.service.content.ShareService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @RestController @RequestMapping("/share") public class ShareController { @Resource private ShareService shareService; @GetMapping("/{id}") @CheckLogin public ShareDTO findById(@PathVariable Integer id, @RequestHeader("X-Token") String token){ return this.shareService.findById(id,token); } }
加上@RequestHeader注解,将请求头的X-Token注入到token,修改相对应的代码逻辑
package com.itmuch.content.feignClient; import com.itmuch.content.domain.dto.user.UserDTO; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestHeader; //@FeignClient(name = "user-center",configuration = UserCenterFeignConfiguration.class) @FeignClient(name = "user-center") // fallback = UserCenterFeignClientFallback.class, // fallbackFactory = UserCenterFeignClientFallbackFactory.class) //用了fallbackFactory就不要用fallback,fallbackFactory会比fallback更加强大,可以返回错误信息; public interface UserCenterFeignClient { /** * 当调用findById时会构造http://user-center/users/{id} * * @param id * @return */ @GetMapping("/users/{id}") UserDTO findUserById(@PathVariable Integer id,@RequestHeader("X-Token") String token); }
优点:操作简单
缺点:现在修改一个API就要修改这么多文件,如果很多API这样实现起来就很不现实
feign的拦截器(RequestInterceptor)
package com.itmuch.content.feignClient.interceptor; import feign.RequestInterceptor; import feign.RequestTemplate; import org.apache.commons.lang3.StringUtils; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; public class TokenRelayRequestIntecepor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { //1、获取token //利用静态方法获取request RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes; HttpServletRequest request = attributes.getRequest(); String token = request.getHeader("X-Token"); //2、传递token //把指定的值放进name里面 if (StringUtils.isNotEmpty(token)) { requestTemplate.header("X-Token",token); } } }
使用拦截器不需要改变其他代码,
配置拦截器有两种方式,可以在@feignclient中使用configuration,或者直接在配置文件中进行全局配置