首页 > 开发 > Java > 正文

Spring-boot结合Shrio实现JWT的方法

2024-07-14 08:40:39
字体:
来源:转载
供稿:网友

本文介绍了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分支 :温馨提示:平时测试代码可能比较乱。

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


注:相关教程知识阅读请移步到JAVA教程频道。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表