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());
@Configuration
public 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异常

@RestControllerAdvice
public 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页面
 */
@RestController
public 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分支:温馨提示:平时测试代码可能比较乱。

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

(0)

相关推荐

  • spring boot+jwt实现api的token认证详解

    前言 本篇和大家分享jwt(json web token)的使用,她主要用来生成接口访问的token和验证,其单独结合springboot来开发api接口token验证很是方便,由于jwt的token中存储有用户的信息并且有加密,所以适用于分布式,这样直接吧信息存储在用户本地减速了服务端存储sessiion或token的压力: 如下快速使用: <!--jwt--> <dependency> <groupId>io.jsonwebtoken</groupId>

  • Spring Boot(四)之使用JWT和Spring Security保护REST API

    通常情况下,把API直接暴露出去是风险很大的,不说别的,直接被机器攻击就喝一壶的.那么一般来说,对API要划分出一定的权限级别,然后做一个用户的鉴权,依据鉴权结果给予用户开放对应的API.目前,比较主流的方案有几种: 用户名和密码鉴权,使用Session保存用户鉴权结果. 使用OAuth进行鉴权(其实OAuth也是一种基于Token的鉴权,只是没有规定Token的生成方式) 自行采用Token进行鉴权 第一种就不介绍了,由于依赖Session来维护状态,也不太适合移动时代,新的项目就不要采用了.

  • Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码

    本人野生程序员一名,了解了一些微服务架构.前后端分离.SPA的知识后就想试试做点什么东西.之前一直做后端,前端只是有基础知识.之前学习过angularjs,但当时就是一脸懵逼(完全看不懂是啥)就放弃了.最近又学了Vue,这次感觉总算明白了一些,但其中也跳过很多坑(应该还会更多),在这里写下来记录一下吧. 说回主题,之前传统登录认证的方法基本是由服务器端提供一个登录页面,页面中的一个form输入username和password后POST给服务器,服务器将这些信息与DB或Ldap中的用户信息对比,

  • 详解Spring Boot实战之Filter实现使用JWT进行接口认证

    本文介绍了spring Boot实战之Filter实现使用JWT进行接口认证,分享给大家 jwt(json web token) 用户发送按照约定,向服务端发送 Header.Payload 和 Signature,并包含认证信息(密码),验证通过后服务端返回一个token,之后用户使用该token作为登录凭证,适合于移动端和api jwt使用流程 本文示例接上面几篇文章中的代码进行编写,请阅读本文的同时可以参考前面几篇文章 1.添加依赖库jjwt,本文中构造jwt及解析jwt都使用了jjwt库

  • 基于Token的身份验证之JWT基础教程

    前言 初次了解JWT,很基础,高手勿喷. 基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session. token应用流程为: 1.初次登录:用户初次登录,输入用户名密码. 2.密码验证:服务器从数据库取出用户名和密码进行验证. 3.生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT. 4.返还JWT:服务器的HTTP RESPONSE中将JWT返还. 5.带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER

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

    本文介绍了Spring-boot结合Shrio实现JWT的方法,分享给大家,具体如下: 关于验证大致分为两个方面: 用户登录时的验证: 用户登录后每次访问时的权限认证 主要解决方法:使用自定义的Shiro Filter 项目搭建: 这是一个spring-boot 的web项目,不了解spring-boot的项目搭建,请google. pom.mx引入相关jar包 <!-- shiro 权限管理 --> <dependency> <groupId>org.apache.s

  • Spring Boot集成MyBatis访问数据库的方法

    基于spring boot开发的微服务应用,与MyBatis如何集成? 集成方法 可行的方法有: 1.基于XML或者Java Config,构建必需的对象,配置MyBatis. 2.使用MyBatis官方提供的组件,实现MyBatis的集成. 方法一 建议参考如下文章,完成集成的验证. MyBatis学习 之 一.MyBatis简介与配置MyBatis+Spring+MySql 基于Spring + Spring MVC + Mybatis 高性能web构建 spring与mybatis三种整合

  • Spring Boot使用Druid和监控配置方法

    Spring Boot默认的数据源是:org.apache.tomcat.jdbc.pool.DataSource Druid是Java语言中最好的数据库连接池,并且能够提供强大的监控和扩展功能. 下面来说明如何在 Spring Boot 中配置使用Druid (1)添加Maven依赖 (或jar包)\ <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId&g

  • 移动开发Spring Boot外置tomcat教程及解决方法

    springboot微服务内置了tomcat,在工程目录下执行:mvn clean package,可以将项目打成jar,通过java -jar jar包名.jar启动项目. 有哪些场景需要将springboot打成war包去部署呢? 1.一个tomcat管理多个项目 2.springboot整合jsp等 解决方法: 1.<packaging>jar</packaging>中的jar改成war 2.引入依赖: <dependency> <groupid>or

  • Spring Boot中使用MongoDB数据库的方法

    MongoDB数据库简介 简介 MongoDB是一个高性能,开源,无模式的,基于分布式文件存储的文档型数据库,由C++语言编写,其名称来源取自"humongous",是一种开源的文档数据库──NoSql数据库的一种.NoSql,全称是 Not Only Sql,指的是非关系型的数据库. 特点 MongoDB数据库的特点是高性能.易部署.易使用,存储数据非常方便.主要功能特性有: * 面向集合存储,易存储对象类型的数据. * 模式自由. * 支持动态查询. * 支持完全索引,包含内部对象

  • Spring Boot 中使用cache缓存的方法

    一.什么是缓存 Cache Cache 一词最早来自于CPU设计 当CPU要读取一个数据时,首先从CPU缓存中查找,找到就立即读取并送给CPU处理:没有找到,就从速率相对较慢的内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存.正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在CPU缓存中,只有大约10%需要从内存读取.这大大节省了CPU直接读取内存的

  • Spring Boot 2.0多数据源配置方法实例详解

    两个数据库实例,一个负责读,一个负责写. datasource-reader: type: com.alibaba.druid.pool.DruidDataSource url: jdbc:mysql://192.168.43.61:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false username: icbc password: icbc driver-class-na

  • 在Spring Boot中实现HTTP缓存的方法

    缓存是HTTP协议的一个强大功能,但由于某些原因,它主要用于静态资源,如图像,CSS样式表或JavaScript文件,但是,HTTP缓存不仅限于这些,还可以将其用于动态计算的资源. 通过少量工作,您可以加快应用程序并改善整体用户体验.在本文中,您将学习 如何使用内置的HTTP响应缓存机制来实现缓存SpringBoot控制器的结果 . 1.如何以及何时使用HTTP响应缓存? 您可以在应用程序的多个层上进行缓存.数据库具有其缓存存储,Web客户端也在其需要重用的信息.HTTP协议负责网络通信.缓存机

  • Spring Boot+maven打war包的方法

    存在一个坑: 官网文档 指出以下前3步做法,但是这样只可以打出可运行的jar包,要打出war包还要在文档后面的链接跳到另一个页面,才能找到第四步的做法,也就是最终能够打出war包,可能有些朋友有些粗心没找到第四步的链接,而以为只做了前三步就可以打出war包了,结果一直出错,还以为自己的业务代码有问题,然后一直瞎折腾,浪费很多时间(比如我),因此我把整个过程写于此. 1.指定war打包方式 <packaging>jar</packaging> 2.pom.xml添加spring-bo

随机推荐