当前位置: 首页 > 工具软件 > oauth2-shiro > 使用案例 >

springboot+jwt+shiro+redis实现微信小程序oauth2.0登录

白通
2023-12-01

思路:
微信小程序获取用户授权后能获取到授权code,
前端调用登录接口传给后端code获取用户openid等信息。
登录接口:通过微信api调用getSessionInfo获取openid等基本信息,
获取成功后判断是否为新用户进行登录/新增用户后登录,
通过jwt生成token与refreshToken,将token与refreshToken存入redis中并设置有效期,然后将token与refreshToken返回给前端。
前端每次调用接口在请求头中传入token。
后端使用shiro自定义filter拦截请求,并解析token进行校验,若token校验失败或者过期则返回固定code给前端。
前端请求收到对应code后调用refreh_token接口传入refreshToken获取新的token。
refreh_token接口:接受到refrehToken参数后进行校验,若校验通过则生成新的token并存入redis;若校验失败或者refrehToken过期则返回失败信息,前端接收到失败的信息后重新调用登录接口进行登录。

以下具体代码:
@GetMapping(“login”)
@ApiOperation(“微信登录”)
public AjaxResult login(String code){

    try {
        WxMaJscode2SessionResult info = wxMaService.getUserService().getSessionInfo(code);
        //这里是通过jdk方式调用微信api
        ResultToken token = new ResultToken();
        User user= userService.queryByOpenId(info.getOpenid());
        if(user==null){
            //保存新用户
            user=new User ();
            user.setOpenId(info.getOpenid());
            user.insert(user);
        }
        token.setUserId(viViuser.getId());
        String userToken = getToken(viViuser);
        String refreshToken = getRefreshToken(viViuser);
        token.setToken(userToken);
        token.setRefresh_token(refreshToken);
        //存入token与refreshToken并设置过期时间,refresh_toke的过期时间至少为token的两倍以上,这里使用指定前缀+userid作为key,token作为value
      redisRepository.setExpire(ViConstants.PREFIX_VI_REDIS_USER_TOKEN+viViuser.getId(),userToken,3600*24);
        redisRepository.setExpire(ViConstants.PREFIX_VI_REDIS_USER_REFRESH_TOKEN+viViuser.getId(),refreshToken,3600*24*7);

        return AjaxResult.success(token);

    } catch (WxErrorException e) {
        e.printStackTrace();
        return AjaxResult.error(e.getError().getErrorMsg());
    }
}
   public String getToken(User user) {
        String token="";
        token= JWT.create().withAudience(String.valueOf(user.getId())).withIssuedAt(new Date())
                .sign(Algorithm.HMAC256(ViConstants.PREFIX_VI_TOKEN_SIGNuser.getOpenId()));
        return token;
    }
public String getRefreshToken(User user) {
    String token="";
    token= JWT.create().withAudience(String.valueOf(user.getId())).withIssuedAt(new Date())
            .sign(Algorithm.HMAC256(ViConstants.PREFIX_VI_REFRESH_TOKEN_SIGN+user.getOpenId()));
    return token;
}

JWTFilter:

public class JWTFilter extends BasicHttpAuthenticationFilter {

@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
    //生成shiro中的token并交给shiro进行登录验证,登录验证过程在userRealm进行重写
    AuthenticationToken token = this.createToken(servletRequest, servletResponse);
    try {
    //交给shiro进行登录
        this.getSubject(servletRequest, servletResponse).login(token);
    } catch (AuthenticationException e) {
    //如果登录失败,返回统一的数据类型json
        HttpServletResponse httpServletResponse = (HttpServletResponse)servletResponse;
        httpServletResponse.setContentType("application/json");
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.getWriter().write(new Gson().toJson(new AjaxResult(AjaxResult.Type.UN_LOGIN,"Unauthorized")) );
        return false;
    }
    return true;
}

/**
      * 支持跨域
      *
      * @param request
      * @param response
      * @return
      * @throws Exception
      */
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    httpServletResponse.setHeader("Access-control-Allow-Origin", "*");
    httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
    httpServletResponse.setHeader("Access-Control-Allow-Headers", "token,Authorization,Origin,X-Requested-With,Content-Type,Accept");

    // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
    if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
        httpServletResponse.setStatus(HttpStatus.OK.value());
        return false;
    }
    return super.preHandle(httpServletRequest, httpServletResponse);

}



@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
//获取token,如果是get请求可从param中获取
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    String token = request.getHeader("token");
    if(request.getMethod().equals(RequestMethod.GET.name())&& StringUtils.isEmpty(token)){
        token=request.getParameter("token");
    }
    return new JWTToken(token);
}

}

UserRealm

@Component
public class AppUserRealm extends AuthorizingRealm {

    @Autowired
   UserService userService ;

    @Autowired
    private RedisRepository redisRepository;

    @Override
    public boolean supports(AuthenticationToken token) {
        return true;
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();
        //TODO: 授权管理
        
        return auth;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authToken) throws org.apache.shiro.authc.AuthenticationException {
        String token = (String) authToken.getCredentials();
        //检查是否有token
        if (StringUtils.isEmpty(token)) {
            throw new ViAuthenticationException("无token,请重新登录");
        }
        // 获取 token 中的 user id
        String userId;
        try {
            userId = JWT.decode(token).getAudience().get(0);
        } catch (JWTDecodeException j) {
            throw new ViAuthenticationException("解析token失败:"+token);
        }
        ViViuser user = viViuserService.selectViViuserById(Long.valueOf(userId));
        if (user == null) {
            throw new ViAuthenticationException("用户不存在,请重新登录");
        }
        // jwt 验证 token
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(ViConstants.PREFIX_VI_TOKEN_SIGN+user.getOpenId())).build();
        try {
            jwtVerifier.verify(token);
        } catch(JWTVerificationException e) {
            throw new ViAuthenticationException("jwt校验失败");
        }
        // 验证 token(redis)
        if(!verifyToken(token,user)){
            throw new ViAuthenticationException("token过期");
        }
        AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user, token, userId);
        return authcInfo;
    }


    boolean verifyToken(String token,User user){
        String cacheToken = redisRepository.get(ViConstants.PREFIX_VI_REDIS_USER_TOKEN+ user.getId());
        //当传入token与缓存中token相等时,说明token有效
        if(!StringUtils.isEmpty(cacheToken)&&cacheToken.equals(token)){
            return true;
        }else {
            return false;
        }
    }
}

//禁用session的配置类

public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {
    @Override
    public Subject createSubject(SubjectContext context) {
        //不创建session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

ShiroConfig

@Configuration
public class ShiroConfig
{


    @Bean(name = "subjectFactory")
    public StatelessDefaultSubjectFactory subjectFactory() {
        StatelessDefaultSubjectFactory statelessDefaultSubjectFactory = new StatelessDefaultSubjectFactory();
        return statelessDefaultSubjectFactory;
    }



    @Bean(name = "sessionManager")
    public DefaultSessionManager sessionManager() {
        DefaultSessionManager sessionManager = new DefaultSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        return sessionManager;
    }


    @Bean(name = "defaultSessionStorageEvaluator")
    public DefaultSessionStorageEvaluator defaultSessionStorageEvaluator () {
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        return defaultSessionStorageEvaluator;
    }


    @Bean(name = "subjectDAO")
    public DefaultSubjectDAO subjectDAO(@Qualifier("defaultSessionStorageEvaluator")DefaultSessionStorageEvaluator defaultSessionStorageEvaluator) {
        DefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();
        defaultSubjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        return defaultSubjectDAO;
    }


    @Bean(name = "securityManager")
    public SecurityManager securityManager(AppUserRealm myRealm, @Qualifier("subjectDAO")DefaultSubjectDAO
            subjectDAO, @Qualifier("sessionManager")DefaultSessionManager sessionManager, @Qualifier("subjectFactory")StatelessDefaultSubjectFactory subjectFactory) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        securityManager.setSubjectDAO(subjectDAO);
        securityManager.setSubjectFactory(subjectFactory);
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }


    public JWTFilter jwtFilter() {
        return new JWTFilter();
    }


    @Bean
    public FilterRegistrationBean delegatingFilterProxy(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilter");
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }


    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //
        LinkedHashMap<String, Filter> filters = new LinkedHashMap<>();
        //拦截链
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //swagger
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger-resources/**", "anon");
        filterChainDefinitionMap.put("/v2/api-docs", "anon");
        filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/csrf", "anon");
        //登录接口
        filterChainDefinitionMap.put("/agent/login", "anon");
        filterChainDefinitionMap.put("/agent/refresh_token", "anon");
        // 对静态资源设置匿名访问
        filterChainDefinitionMap.put("/favicon.ico**", "anon");
        filters.put("jwtFilter", jwtFilter());
        shiroFilterFactoryBean.setFilters(filters);
        filterChainDefinitionMap.put("/**", "jwtFilter");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    @Bean(name = "advisorAutoProxyCreator")
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean(name = "authorizationAttributeSourceAdvisor")
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

JWTToken

public class JWTToken extends UsernamePasswordToken {
    public JWTToken() {
        super();
    }
    public JWTToken(String token) {
        super(token,token, false, null);
    }
}

最后是refreshToken接口

 @GetMapping("refresh_token")
    @ApiOperation("刷新token")
    public AjaxResult refresh_token(String refreshToken){
        try {
            String userId;
            try {
                userId = JWT.decode(refreshToken).getAudience().get(0);
            } catch (JWTDecodeException j) {
                throw new ViAuthenticationException("解析refresh_token失败:"+refreshToken);
            }
            User user = userService.selectById(Long.valueOf(userId));
            if (user == null) {
                throw new ViAuthenticationException("用户不存在,请重新登录");
            }
            // jwt 验证 refresh_token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(ViConstants.PREFIX_VI_REFRESH_TOKEN_SIGN+user.getOpenId())).build();
            try {
                jwtVerifier.verify(refreshToken);
            } catch(JWTVerificationException e) {
                throw new ViAuthenticationException("jwt校验失败");
            }
            if(!verifyRefreshToken(refreshToken,user)){
                throw new ViAuthenticationException("refresh_token过期");
            }
            String token = getToken(user);
            TokenResult result=new TokenResult();
            result.setToken(token);
            //新的token存入redis中
          redisRepository.setExpire(ViConstants.PREFIX_VI_REDIS_USER_TOKEN+user.getId(),token,3600*24);
            return AjaxResult.success(result);

        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error(e.getMessage());
        }

    }
boolean verifyRefreshToken(String refresh_token,User user){
        String cacheToken = redisRepository.get(ViConstants.PREFIX_VI_REDIS_USER_REFRESH_TOKEN+ user.getId());
        //当传入token与缓存中token相等时,说明token有效
        if(!StringUtils.isEmpty(cacheToken)&&cacheToken.equals(refresh_token)){
            return true;
        }else {
            return false;
        }
    }
 类似资料: