虚位以待(AD)
虚位以待(AD)
首页 > 软件编程 > Java编程 > Spring-boot结合Shrio实现JWT的方法

Spring-boot结合Shrio实现JWT的方法
类别:Java编程   作者:码皇   来源:互联网   点击:

这篇文章主要介绍了Spring-boot结合Shrio实现JWT的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

本文介绍了Spring-boot结合Shrio实现JWT的方法,分享给大家,具体如下:

关于验证大致分为两个方面:

  1. 用户登录时的验证;
  2. 用户登录后每次访问时的权限认证

主要解决方法:使用自定义的Shiro Filter

项目搭建:

这是一个spring-boot 的web项目,不了解spring-boot的项目搭建,请google。

pom.mx引入相关jar包

    <!-- shiro 权限管理 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${
    shiro.version}
    </version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${
    shiro.version}
    </version> </dependency> <!-- JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>

Shrio 的相关配置

划重点!!自定义了一个Filter

    filterMap.put("JWTFilter", new JWTFilter());
    @Configurationpublic class ShiroConfig {
    @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    // 添加自己的过滤器并且取名为JWTFilter Map<String, Filter> filterMap = new HashMap<>();
    filterMap.put("JWTFilter", new JWTFilter());
    shiroFilterFactoryBean.setFilters(filterMap);
    /* * 自定义url规则 * http://shiro.apache.org/web.html#urls- */ Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
    filterChainDefinitionMap.put("/**", "JWTFilter");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
    }
    /** * securityManager 不用直接注入shiroDBRealm,可能会导致事务失效 * 解决方法见 handleContextRefresh * http://www.debugrun.com/a/NKS9EJQ.html */ @Bean("securityManager") public DefaultWebSecurityManager securityManager(TokenRealm tokenRealm) {
    DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
    manager.setRealm(tokenRealm);
    /* * 关闭shiro自带的session,详情见文档 * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29 */ DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
    DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
    defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
    subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
    manager.setSubjectDAO(subjectDAO);
    return manager;
    }
    @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
    }
    @Bean(name = "TokenRealm") @DependsOn("lifecycleBeanPostProcessor") public TokenRealm tokenRealm() {
    return new TokenRealm();
    }
    @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    // 强制使用cglib,防止重复代理和可能引起代理出错的问题 // https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    return defaultAdvisorAutoProxyCreator;
    }
    @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return new AuthorizationAttributeSourceAdvisor();
    }
    }

自定义Shrio filter

执行顺序:preHandle -> doFilterInternal -> executeLogin -> onLoginSuccess

主要判断是不是登录请求的是 doFilterInternal

    public class JWTFilter extends BasicHttpAuthenticationFilter {
    /** * 自定义执行登录的方法 */ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    UsernamePasswordToken usernamePasswordToken = JSON.parseObject(httpServletRequest.getInputStream(), UsernamePasswordToken.class);
    // 提交给realm进行登入,如果错误他会抛出异常并被捕获 Subject subject = this.getSubject(request, response);
    subject.login(usernamePasswordToken);
    return this.onLoginSuccess(usernamePasswordToken, subject, request, response);
    //错误抛出异常 }
    /** * 最先执行的方法 */ @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
    return super.preHandle(request, response);
    }
    /** * 登录成功后登录的操作 * 加上jwt 的header */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) {
    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
    String jwtToken = Jwts.builder() .setId(token.getPrincipal().toString()) .setExpiration(DateTime.now().plusMinutes(30).toDate()) .signWith(SignatureAlgorithm.HS256, JWTCost.signatureKey) .compact();
    httpServletResponse.addHeader(AUTHORIZATION_HEADER, jwtToken);
    return true;
    }
    /** * 登录以及校验的主要流程 * 判断是否是登录,或者是登陆后普通的一次请求 */ @Override public void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
    HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
    String servletPath = httpServletRequest.getServletPath();
    if (StringUtils.equals(servletPath, "/login")) {
    //执行登录 this.executeLogin(servletRequest, servletResponse);
    }
    else {
    String authenticationHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
    if (StringUtils.isNotEmpty(authenticationHeader)) {
    Claims body = Jwts.parser() .setSigningKey(JWTCost.signatureKey) .parseClaimsJws(authenticationHeader) .getBody();
    if (body != null) {
    //更新token body.setExpiration(DateTime.now().plusMinutes(30).toDate());
    String updateToken = Jwts.builder().setClaims(body).compact();
    httpServletResponse.addHeader(AUTHORIZATION_HEADER, updateToken);
    //添加用户凭证 PrincipalCollection principals = new SimplePrincipalCollection(body.getId(), JWTCost.UserNamePasswordRealm);
    //拼装shiro用户信息 WebSubject.Builder builder = new WebSubject.Builder(servletRequest, servletResponse);
    builder.principals(principals);
    builder.authenticated(true);
    builder.sessionCreationEnabled(false);
    WebSubject subject = builder.buildWebSubject();
    //塞入容器,统一调用 ThreadContext.bind(subject);
    filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
    }
    else {
    httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
    }
    }
    }
    }

登录失败处理

处理Shrio异常

    @RestControllerAdvicepublic class GlobalControllerExceptionHandler {
    @ExceptionHandler(value = Exception.class) public Object allExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    String message = exception.getCause().getMessage();
    LogUtil.error(message);
    return new ResultInfo(exception.getClass().getName(), message);
    }
    /*=========== Shiro 异常拦截==============*/ @ExceptionHandler(value = IncorrectCredentialsException.class) public String IncorrectCredentialsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return "IncorrectCredentialsException";
    }
    @ExceptionHandler(value = UnknownAccountException.class) public String UnknownAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return "UnknownAccountException";
    }
    @ExceptionHandler(value = LockedAccountException.class) public String LockedAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return "LockedAccountException";
    }
    @ExceptionHandler(value = ExcessiveAttemptsException.class) public String ExcessiveAttemptsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return "ExcessiveAttemptsException";
    }
    @ExceptionHandler(value = AuthenticationException.class) public String AuthenticationException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return "AuthenticationException";
    }
    @ExceptionHandler(value = UnauthorizedException.class) public String UnauthorizedException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return "UnauthorizedException";
    }
    }

处理JWT异常

这是个坑,因为是在filter内发生的异常,@ExceptionHandler是截获不到的。

    /** * 截获spring boot Error页面 */@RestControllerpublic class GlobalExceptionHandler implements ErrorController {
    @Override public String getErrorPath() {
    return "/error";
    }
    @RequestMapping(value = "/error") public Object error(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 错误处理逻辑 Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
    Throwable cause = exception.getCause();
    if (cause instanceof ExpiredJwtException) {
    response.setStatus(HttpStatus.GATEWAY_TIMEOUT.value());
    return new ResultInfo("ExpiredJwtException", cause.getMessage());
    }
    if (cause instanceof MalformedJwtException) {
    response.setStatus(HttpStatus.FORBIDDEN.value());
    return new ResultInfo("MalformedJwtException", cause.getMessage());
    }
    return new ResultInfo(cause.getCause().getMessage(), cause.getMessage());
    }
    }

关于权限等授权信息,可以直接放到Redis中实现缓存。我认为也是不错的。

源码奉上:githup-shiro分支 :温馨提示:平时测试代码可能比较乱。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

您可能感兴趣的文章:

  • 详解Spring Boot实战之Filter实现使用JWT进行接口认证
  • Spring Boot(四)之使用JWT和Spring Security保护REST API
相关热词搜索: Springboot实现JWT Spring boot Shrio Spr