文章目录

  • 一、前情提要
  • 二、整合Shiro与JWT
    • 1、编写JWT工具类
    • 2、编写JWTToken
    • 3、编写JWTFilter
    • 4、编写JWTRealm
    • 5、编写Shiro配置类
  • 三、编写异常处理类
  • 四、编写Controller

一、前情提要

JWT:服务端根据规范生成一个令牌(token),并且发放给客户端(保存在客户端)。此时客户端请求服务端的时候就可以携带者令牌,以令牌来证明自己的身份信息。

Shiro:Java的一个安全(权限)框架,用户登录时把身份信息(用户名/手机号/邮箱地址等)和凭证信息(密码/证书等)封装成一个Token令牌,通过安全管理器中的认证器进行校验,成功则授权以访问系统.(详细描述可以参考文章:Shiro基础)

可以看到Shiro本身是可以实现认证功能的,默认Shiro是在subject.login()后将认证状态存入全局session中, 之后的请求会从这个session中拿这个登录状态。而我们在这里将Shiro的认证部分交给JWT去做以抛弃session的使用(为什么要这么做可以参考JWT技术–JSON Web Token),即不使用Shiro自带的UsernamePasswordToken,而是让JWT来生成token,登录时自然也不用通过shiro的subject.login()方法,只需要对请求携带的token进行验证就行。

二、整合Shiro与JWT

1、编写JWT工具类

作用

  • 生成token
  • 验证token
public class JWTUtils {private static final long EXPIRE = 30 * 60 * 1000;private static final String SECRET = "!ad#12~";public static String getToken(User user) {JWTCreator.Builder builder = JWT.create();return builder.withClaim("email", user.getUsername()).withClaim("role", user.getRole()).withExpiresAt(new Date(System.currentTimeMillis() + EXPIRE)).sign(Algorithm.HMAC256(SECRET));}public static DecodedJWT verify(String token) {return JWT.require(Algorithm.HMAC256(SECRET)).build().verify(token);}}

2、编写JWTToken

作用

  • 取代原生token

正常使用shiro时是通过使用其自带的UsernamePasswordToken(如subject.login(new UsernamePasswordToken(username,password));

这里我们选择使用JWT来生成token,那么就要编写一个JWTToken类来取代UsernamePasswordToken

需要实现AuthenticationToken类并重写getPrincipal()和getCredentials()两个方法(用本是用来获得用户名和密码的,这里直接将token返回)

public class JWTToken implements AuthenticationToken {private String token;public JWTToken(String token) {this.token = token;}@Overridepublic Object getPrincipal() {return token;}@Overridepublic Object getCredentials() {return token;}
}

3、编写JWTFilter

目的

  • 过滤请求
  • 封装subject.login()

shiro通过传入用户名和密码生成UsernamePasswordToken后使用subject.login进行登录,这里我们使用了JWT,token是通过是通过前台请求传过来的而不是后台自己通过账号密码生成的,所以我们需要编写一个过滤器对请求进行判断(如是否携带token,token是否合法,过滤option请求等)

shiro内置了认证过滤器,我们只需对其中的几个方法进行重写

public class JWTFilter extends BasicHttpAuthenticationFilter {/* * 过滤器执行流程:* isAccessAllowed()->isLoginAttempt()->executeLogin()*/// 是否允许访问@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {if (isLoginAttempt(request, response)) {// 有认证意愿try {executeLogin(request, response);return true;} catch (Exception e) {// token错误responseError(response,e.getMessage());}}// 没有认证意愿(可能是登录行为或者为游客访问),放行// 此处放行是因为有些操作不需要权限也可以执行,而对于那些需要权限才能执行的操作自然会因为没有token而在权限鉴定时被拦截return true;}// 是否有认证意愿(即是否携带token)@Overrideprotected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String token = httpServletRequest.getHeader("token");return token != null;}// 执行认证@Overrideprotected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpServletRequest = (HttpServletRequest) request;String token = httpServletRequest.getHeader("token");JWTToken jwt = new JWTToken(token);// 使用自定义的JWTToken而不是默认的UsernamePasswordTokengetSubject(request, response).login(jwt);// 调用了realm中的认证方法,没有出现异常则证明认证成功return true;}@Overrideprotected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest req= (HttpServletRequest) request;HttpServletResponse res= (HttpServletResponse) response;res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));// 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {res.setStatus(HttpStatus.OK.value());// 返回true则继续执行拦截链,返回false则中断后续拦截,直接返回,option请求显然无需继续判断,直接返回return false;}return super.preHandle(request, response);}// 非法请求跳转private void responseError(ServletResponse response, String msg) {HttpServletResponse httpServletResponse = (HttpServletResponse) response;try {// msg封装为get请求的请求参数,即拼接在url后面,对于中文信息需要进行utf-8编码msg = URLEncoder.encode(msg, StandardCharsets.UTF_8);// 跳转至控制器unauthorizedhttpServletResponse.sendRedirect("/unauthorized/" + msg);} catch (IOException e) {System.out.println(e.getMessage());   }}
}

4、编写JWTRealm

目的

  • 用于进行权限信息的验证
  • 让Shiro支持JWTToken

在JWTFIlter中,我们对于携带了token的请求会执行executeLogin方法,该方法封装了subject.login(token)方法,而login方法其实就是通过我们定义的realm对传入的token进行权限信息的验证

我们首先需要让Shiro支持我们自定义的token

随后重写doGetAuthenticationInfo方法用于认证,重写doGetAuthorizationInfo方法用于授权

如果鉴权或者认证未通过则上面的filter会出现异常,进而拦截这次请求跳转至unauthorized控制器

@Component
public class JWTRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;// 让shiro支持我们自定义的token,即如果传入的token时JWTToken则放行// 必须重写不然shiro会报错@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JWTToken;}// 检验权限时调用@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {DecodedJWT verify = JWTUtils.verify(principalCollection.toString());String email = verify.getClaim("email").asString();// 根据email查询用户的身份和权限User user = userService.selectByEmail(email);SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addRole(user.getRole());info.addStringPermissions(user.getPermission());return info;}// 认证和鉴权时调用@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {String token = (String) authenticationToken.getCredentials();// 重写了该类,实际上返回的是tokenString email = null;try {// 根据token获得登录用户的emailemail = JWTUtils.verify(token).getClaim("email").asString();} catch (Exception e) {throw new AuthenticationException("该token非法,可能被篡改或过期");}if (userService.selectByEmail(email) == null) {throw new AuthenticationException("用户不存在");}return new SimpleAuthenticationInfo(token, token, this.getName());}
}

5、编写Shiro配置类

目的

  • 添加过滤器和过滤规则
  • 为默认的安全管理器绑定我们编写的realm并关闭session
  • 添加注解权限开发
@Configuration
public class ShiroConfig {@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);// 添加自己的过滤器Map<String, Filter> filterMap = new HashMap<>();filterMap.put("jwt", new JWTFilter());shiroFilterFactoryBean.setFilters(filterMap);// 设置无权限时跳转urlshiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized/无权限");// 编写过滤规则Map<String, String> filterRuleMap = new HashMap<>();// 访问 /unauthorized/**时直接放行filterRuleMap.put("/unauthorized/**","anon");// 其他所有请求通过我们自己的JWT FilterfilterRuleMap.put("/**", "jwt");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);return shiroFilterFactoryBean;}@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(JWTRealm jwtRealm) {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();defaultWebSecurityManager.setRealm(jwtRealm);// 关闭sessionDefaultSubjectDAO defaultSubjectDAO = new DefaultSubjectDAO();DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();sessionStorageEvaluator.setSessionStorageEnabled(false);defaultSubjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);defaultWebSecurityManager.setSubjectDAO(defaultSubjectDAO);return defaultWebSecurityManager;}/*** 添加注解支持,如果不加的话很有可能注解失效*/@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return defaultAdvisorAutoProxyCreator;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager){AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(defaultWebSecurityManager);return advisor;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}}

三、编写异常处理类

在过滤请求过程中,对于不合法的请求我们进行拦截并抛出了异常,在前后端分离的项目中,我们应该将请求的处理结果(如无权限等信息)返回给前端,而不是直接进行页面跳转。所以我们需要编写一个异常处理类捕获抛出的异常,并根据不同的异常返回给前端相应的信息,让前端进行页面跳转。

@ControllerAdvice
public class ControllerExceptionHandler {// 捕捉shiro的异常@ExceptionHandler(ShiroException.class)@ResponseStatus(HttpStatus.UNAUTHORIZED)@ResponseBodypublic Result handle401(ShiroException e) {return Result.error(ErrorCodeEnum.UNAUTHORIZED);}// 捕捉未认证的异常@ExceptionHandler(UnauthenticatedException.class)@ResponseStatus(HttpStatus.UNAUTHORIZED)@ResponseBodypublic Result handle401(UnauthenticatedException e) {return Result.error(ErrorCodeEnum.UNAUTHORIZED);}// 捕捉token过期异常@ExceptionHandler(value = TokenExpiredException.class)@ResponseStatus(HttpStatus.BAD_REQUEST)@ResponseBodypublic Result handleTokenExpired(TokenExpiredException e) {return Result.error(ErrorCodeEnum.TOKEN_VERIFY_FAIL);}}

四、编写Controller

记得编写处理未认证请求的控制器

@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@PostMapping("register")public Result register(@RequestBody UserBody userBody) {return userService.register(userBody);}@PostMapping("login")public Result login(@RequestBody User user) {// 在编写业务逻辑时应该在结果集中封装token,使得下次发送请求时可以携带tokenreturn userService.login(user);}// 处理未认证请求的控制器@GetMapping("/unauthorized/{message}")public Result unauthorized(@PathVariable("message") String message) {return Result.error(message);}@GetMapping("guest")public Result guest(){return Result.success("guest");}@RequiresRoles("普通用户")@GetMapping("delete")public Result delete(){return Result.success("delete");}
}

测试结果

访问不需要权限的请求(不需要登录(无token)就可以执行的请求)——操作成功

访问需要权限的请求(控制器上添加了类似@RequiresRoles(“普通用户”)的注解)——请求失败

因为发送请求时没有携带token,故没有执行该行为的权限

Shiro整合JWT实现认证和权限鉴定(执行流程清晰详细)相关推荐

  1. Springboot -Shiro整合JWT(注解形式)

    Springboot -Shiro整合JWT(注解形式) 在这里只展示核心代码,具体的请访问github 参考timo 依赖导入 <dependencies><dependency& ...

  2. Spring Boot(十四):spring boot整合shiro-登录认证和权限管理

    Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...

  3. SpringBoot整合Shiro搭建登录注册认证授权权限项目模板

    主要内容: 1 SpringBoot整合Shiro安全框架; 2 Shiro主要学习内容总结;(执行流程.主要对象接口.注意事项等) 3 Redis实现对权限信息缓存; ! 温馨提示: 想要快速搭Sh ...

  4. SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

    文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...

  5. shiro包_Spring Boot 整合 Shiro-登录认证和权限管理

    这篇文章我们来学习如何使用 Spring Boot 集成 Apache Shiro .安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求.在 Java 领域一般有 Spring S ...

  6. java shiro jwt_Springboot实现Shiro整合JWT的示例代码

    写在前面 之前想尝试把JWT和Shiro结合到一起,但是在网上查了些博客,也没太有看懂,所以就自己重新研究了一下Shiro的工作机制,然后自己想了个(傻逼)办法把JWT和Shiro整合到一起了 另外接 ...

  7. SpringBoot中使用Shiro和JWT做认证和鉴权

    最近新做的项目中使用了shiro和jwt来做简单的权限验证,在和springboot集成的过程中碰到了不少坑.做完之后对shiro的体系架构了解的也差不多了,现在把中间需要注意的点放出来,给大家做个参 ...

  8. Shiro整合JWT:解决jwt注销和续签的问题

    文章目录 1. 场景一:token的注销问题(黑名单) 2. 场景二:token的续签问题 3. 项目中的实现 3.1 封装JWT工具类 3.2 配置Shiro的自定义认证类 3.3 登录和退出登录( ...

  9. Spring Boot:整合Shiro-登录认证和权限管理

    一.Apache Shiro 1.什么是 Apache Shiro? Apache Shiro 是一个功能强大.灵活的,开源的安全框架.它可以干净利落地处理身份验证.授权.企业会话管理和加密. Apa ...

  10. (转)Spring Boot (十四): Spring Boot 整合 Shiro-登录认证和权限管理

    http://www.ityouknow.com/springboot/2017/06/26/spring-boot-shiro.html 这篇文章我们来学习如何使用 Spring Boot 集成 A ...

最新文章

  1. php ajax替换数据,如何用ajax替换php函数
  2. 僵尸(bot)程序缓解
  3. 碰到Maven依赖冲突,想砸电脑?这个IDEA插件必须了解一下...
  4. windows 下实现函数打桩:拦截API方式
  5. spring boot自动配置之jdbc
  6. 【Linux】ubuntu系统VMware Tools(文件共享、全屏...)3步完成安装过程亲测可用
  7. 如何使用Javascript 访问local部署的YAAS service
  8. linux的spio在服务器间,scp 将数据从一台linux服务器复制到另一台linux服务器
  9. adc0809引脚图及功能_80C51单片机的引脚及其功能介绍
  10. w ndows7与XP哪个好,windows7和xp哪个好 windows7好用吗
  11. Unsupported major.minor version 51.0
  12. 22 副为程序员定制的对联,总有一副适合你...流泪
  13. PCIE设备与HOST之间的地址转换
  14. 国开文学英语赏析 2021春(2021年7月)
  15. 数据库防火墙技术展望【终章】
  16. mapbox的矢量切片工具:tippecanoe
  17. 由于我的BoBo日志需要天气内容,所以在这里留个脚印。
  18. 华为MA5626配置为普通交换机的方法
  19. 【电子电路】(1)PWM转DAC如何实现
  20. 如何利用人性的弱点在互联网中找到利润高的项目

热门文章

  1. java毕业设计针织企业外包系统Mybatis+系统+数据库+调试部署
  2. java研发微博营销
  3. Office2010安装出错(Error1406)
  4. Linux操作系统优化
  5. 论文主题、引用量、中国机构 华人学者,KDD 2020 关键数据抢先看
  6. Linux的命名空间
  7. 调整Exchange接收连接器延迟参数解决SMTP代发送邮件问题
  8. IEEE期刊最新的影响因子
  9. c语言关于数组排序法和插入一个数的详细讲解
  10. HTML吸引人眼球的网页,这8个神奇的HTML5文字特效让你的网页抓人眼球