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

在这里只展示核心代码,具体的请访问github

参考timo

依赖导入

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId><exclusions><exclusion><artifactId>spring-boot-starter-logging</artifactId><groupId>org.springframework.boot</groupId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 配置 JWT --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.10.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.5.1</version></dependency><!-- 引入log4j2依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId></dependency><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.2</version><scope>compile</scope></dependency>
</dependencies>

日志配置点击这里

Jwt工具类

@Slf4j
public class JwtUtil {/*** 生成JwtToken** @param username 用户名* @param secret   秘钥* @param amount   过期天数*/public static String getToken(String username, String secret, int amount) {User user = new User();user.setUsername(username);return getToken(user, secret, amount);}/*** 生成JwtToken** @param user   用户对象* @param secret 秘钥* @param amount 过期天数*/public static String getToken(User user, String secret, int amount) {// 过期时间Calendar ca = Calendar.getInstance();ca.add(Calendar.DATE, amount);// 随机ClaimString random = getRandomString(6);// 创建JwtToken对象String token = "";token = JWT.create()// 用户名.withSubject(user.getUsername())// 发布时间.withIssuedAt(new Date())// 过期时间.withExpiresAt(ca.getTime())// 自定义随机Claim.withClaim("ran", random).sign(getSecret(secret, random));return token;}/*** 获取请求对象中的token数据*/public static String getRequestToken(HttpServletRequest request) {// 获取JwtTokens失败String authorization = request.getHeader("authorization");log.info("token=========>" + authorization);if (authorization == null || !authorization.startsWith("Bearer ")) {throw new ResultException(JwtResultEnums.TOKEN_ERROR);}//因为有前缀,所以要去掉前缀return authorization.substring(7);}/*** 获取当前token中的用户名*/public static String getSubject() {HttpServletRequest request = getRequest();String token = getRequestToken(request);return JWT.decode(token).getSubject();}/*** 验证JwtToken** @param token JwtToken数据* @return true 验证通过* @throws TokenExpiredException    Token过期* @throws JWTVerificationException 令牌无效(验证不通过)*/public static void verifyToken(String token, String secret) throws JWTVerificationException {String ran = JWT.decode(token).getClaim("ran").asString();log.info("验证JwtToken");JWTVerifier jwtVerifier = JWT.require(getSecret(secret, ran)).build();jwtVerifier.verify(token);}/*** 生成Secret混淆数据*/private static Algorithm getSecret(String secret, String random) {String salt = "君不见黄河之水天上来,奔流到海不复回。君不见高堂明镜悲白发,朝如青丝暮成雪。";//String salt = "元嘉草草,封狼居胥,赢得仓皇北顾。四十三年,望中犹记,烽火扬州路。可堪回首,佛狸祠下,一片神鸦社鼓。凭谁问、廉颇老矣,尚能饭否?";//String salt = "安能摧眉折腰事权贵,使我不得开心颜。";//String salt = "大江东去,浪淘尽,千古风流人物。故垒西边,人道是,三国周郎赤壁。乱石穿空,惊涛拍岸,卷起千堆雪。江山如画,一时多少豪杰。";return Algorithm.HMAC256(secret + salt + "(ノ ̄▽ ̄)ノ 皮一下" + random);}/*** 获取随机位数的字符串** @param length 随机位数*/public static String getRandomString(int length) {Random random = new Random();StringBuilder sb = new StringBuilder();for (int i = 0; i < length; i++) {// 获取ascii码中的字符 数字48-57 小写65-90 大写97-122int range = random.nextInt(75) + 48;range = range < 97 ? (range < 65 ? (range > 57 ? 114 - range : range) : (range > 90 ? 180 - range : range)) : range;sb.append((char) range);}return sb.toString();}}
/*** 获取HttpServlet子对象*/
public class HttpServletUtil {/*** 获取ServletRequestAttributes对象*/public static ServletRequestAttributes getServletRequest(){return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();}/*** 获取HttpServletRequest对象*/public static HttpServletRequest getRequest(){return getServletRequest().getRequest();}/*** 获取HttpServletResponse对象*/public static HttpServletResponse getResponse(){return getServletRequest().getResponse();}/*** 获取请求参数*/public static String getParameter(String param){return getRequest().getParameter(param);}/*** 获取请求参数,带默认值*/public static String getParameter(String param, String defaultValue){String parameter = getRequest().getParameter(param);return StringUtils.isEmpty(parameter) ? defaultValue : parameter;}/*** 获取请求参数转换为int类型*/public static Integer getParameterInt(String param){return Integer.valueOf(getRequest().getParameter(param));}/*** 获取请求参数转换为int类型,带默认值*/public static Integer getParameterInt(String param, Integer defaultValue){return Integer.valueOf(getParameter(param, String.valueOf(defaultValue)));}
}

自定义注解

/*** jwt权限注解(需要权限)*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface JwtPermissions {}
/*** 忽略jwt权限验证注解(只在拦截的地址内有效 /api/**)*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface IgnorePermissions {}

通过AOP控制横切,控制访问

/*** Jwt权限注解AOP* 通过注解拦截,只需要在需要拦截的接口上添加@JwtPermissions即可*/
@Aspect
@Component
// @ConditionalOnProperty读取yml配置文件里面的字段值
@ConditionalOnProperty(name = "project.jwt.pattern-anno", havingValue = "true", matchIfMissing = true)
public class JwtPermissionsAop {@Autowiredprivate JwtProjectProperties jwtProperties;@Autowiredprivate HttpServletRequest request;@Pointcut("@annotation(com.site.jwt.annotation.JwtPermissions)")public void jwtPermissions() {}@Around("jwtPermissions()")public Object doPermission(ProceedingJoinPoint point) throws Throwable {// 获取请求对象头部token数据String token = JwtUtil.getRequestToken(request);// 验证token数据是否正确try {JwtUtil.verifyToken(token, jwtProperties.getSecret());} catch (TokenExpiredException e) {throw new ResultException(JwtResultEnums.TOKEN_EXPIRED);} catch (JWTVerificationException e) {throw new ResultException(JwtResultEnums.TOKEN_ERROR);}return point.proceed();}
}
/*** jwt权限拦截器**/
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProjectProperties jwtProperties;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果不是映射到方法直接通过if (!(handler instanceof HandlerMethod)) {return true;}// 判断请求映射的方式是否忽略权限验证HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();if (method.isAnnotationPresent(IgnorePermissions.class)) {return true;}// 获取请求对象头部token数据String token = JwtUtil.getRequestToken(request);// 验证token数据是否正确try {JwtUtil.verifyToken(token, jwtProperties.getSecret());} catch (TokenExpiredException e) {throw new ResultException(JwtResultEnums.TOKEN_EXPIRED);} catch (JWTVerificationException e) {throw new ResultException(JwtResultEnums.TOKEN_ERROR);}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}

Shiro拦截器

/*** 处理session超时问题拦截器*/
@Slf4j
public class UserAuthFilter extends AccessControlFilter {@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {log.info("=========isAccessAllowed=====");if (isLoginRequest(request, response)) {return true;} else {Subject subject = getSubject(request, response);//游客,未登录 falsereturn subject.getPrincipal() != null && (subject.isRemembered() || subject.isAuthenticated());}}/*** 请求头为空,那么就会重定向到登陆* @param request* @param response* @return* @throws Exception*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpRequest = WebUtils.toHttp(request);HttpServletResponse httpResponse = WebUtils.toHttp(response);log.info("=========onAccessDenied======");if (httpRequest.getHeader("X-Requested-With") != null&& "XMLHttpRequest".equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {httpResponse.sendError(HttpStatus.UNAUTHORIZED.value());} else {redirectToLogin(request, response);}return false;}
}
/*** 自定义realm*/
@Slf4j
public class CustomRealm extends AuthorizingRealm {private static HashMap<String, String> map = new HashMap<>();//模拟数据库  密码都是123static {//根据用户名从数据库获取密码 MD5Pwd("root","123")// 335c38d67ad98270cd236398be193804=(lenyuqin,123)// c7b5a4b3d344cd1ee759b954b6a2e75d=(guest,123)// 4fbe67902ad1551ceaf1b971bbca64ca=(root,123)map.put("lenyuqin", "335c38d67ad98270cd236398be193804");map.put("guest", "c7b5a4b3d344cd1ee759b954b6a2e75d");map.put("root", "4fbe67902ad1551ceaf1b971bbca64ca");}/*** 限定这个 Realm 只处理 UsernamePasswordToken*/@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof UsernamePasswordToken;}/*** 查询数据库,将获取到的用户安全数据封装返回*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {log.warn("-------CustomRealm身份认证方法--------");// 从 AuthenticationToken 中获取当前用户String username = (String) token.getPrincipal();log.info("username======>"+username);String pwd = map.get(username);// 用户不存在if (pwd == null) {throw new UnknownAccountException("用户不存在!");}// 使用用户名作为盐值ByteSource credentialsSalt = ByteSource.Util.bytes(username + "salt");/*** 将获取到的用户数据封装成 AuthenticationInfo 对象返回,此处封装为 SimpleAuthenticationInfo 对象。*  参数1. 认证的实体信息,可以是从数据库中获取到的用户实体类对象或者用户名*  参数2. 查询获取到的登录密码*  参数3. 盐值*  参数4. 当前 Realm 对象的名称,直接调用父类的 getName() 方法即可*/SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, pwd, credentialsSalt,getName());return info;}/*** 查询数据库,将获取到的用户的角色及权限信息返回*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {log.info("-------身份授权方法--------");String username = (String) SecurityUtils.getSubject().getPrincipal();log.info("username===========>" + username);SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();Set<String> stringSet = new HashSet<>();Set<String> roleSet = new HashSet<>();if ("lenyuqin".equals(username)) {stringSet.add("user:vip1");stringSet.add("user:vip2");}if ("guest".equals(username)) {stringSet.add("user:vip2");stringSet.add("user:vip3");}if ("root".equals(username)) {stringSet.add("user:vip1");stringSet.add("user:vip2");stringSet.add("user:vip3");}info.setStringPermissions(stringSet);return info;}}
/*** config配置过程* realm 对象的创建 (自定义)*/
@Configuration
public class ShiroConfig {@Beanpublic ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//设置安全管理器shiroFilterFactoryBean.setSecurityManager(securityManager);shiroFilterFactoryBean.setLoginUrl("/login");shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");/** 添加自定义拦截器,重写user认证方式,处理session超时问题*/HashMap<String, Filter> myFilters = new HashMap<>(16);myFilters.put("userAuth", new UserAuthFilter());shiroFilterFactoryBean.setFilters(myFilters);//添加过滤器Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 过滤规则// authc:所有url都必须认证通过才可以访问;// anon:所有url都都可以匿名访问;// user: 必须拥有记住我功能才能用;// perms:拥有对某个资源的权限才能访问;// roles:拥有某个角色权限才能访问filterChainDefinitionMap.put("/toLogin", "anon");filterChainDefinitionMap.put("/login", "anon");filterChainDefinitionMap.put("/", "anon");filterChainDefinitionMap.put("/qinjiang/**", "anon");//filterChainDefinitionMap.put("/level1/**", "authc");//filterChainDefinitionMap.put("/level2/**", "authc");//filterChainDefinitionMap.put("/level3/**", "authc");//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证filterChainDefinitionMap.put("/**", "userAuth");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);// 添加自己的过滤器并且取名为jwtreturn shiroFilterFactoryBean;}@Beanpublic DefaultWebSecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 2.RealmsecurityManager.setRealm(customRealm());/** 关闭shiro自带的session,详情见文档,整合springboot就把下面注释掉* 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);//securityManager.setSubjectDAO(subjectDAO);return securityManager;}/*** CustomRealm 配置,需实现 Realm 接口*/@BeanCustomRealm customRealm() {CustomRealm customRealm = new CustomRealm();// 设置加密算法customRealm.setCredentialsMatcher(hashedCredentialsMatcher());customRealm.setCachingEnabled(false);return customRealm;}@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** ** 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证* ** 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能* * @return*/@Bean@DependsOn({"lifecycleBeanPostProcessor"})public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());return authorizationAttributeSourceAdvisor;}//这里配置了加密算法@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();// 散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列的次数,比如散列两次,相当于 md5(md5(""));hashedCredentialsMatcher.setHashIterations(2);// storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);return hashedCredentialsMatcher;}//开启shiro和thymeleaf的注解@Beanpublic ShiroDialect shiroDialect() {return new ShiroDialect();}}

实体类对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {private String remember;private String username;private String password;
}

前端主要是要带着请求头发出对应的请求,要把token储存起来

这里用了封装好的ajax请求工具详情点击这里

登陆请求

<script type="text/javascript">function login() {// console.log(getFormData(form1))CoreUtil.sendAjax("/login", JSON.stringify(getFormData(form1)), function (res) {console.log(res.data);CoreUtil.setData("access_token", res.data.access_token);CoreUtil.setData("refresh_token", res.data.access_token);window.location.href = "/";})};//jquery 获取form表单数据通用方法function getFormData(formId){var data = {};var results = $(formId).serializeArray();$.each(results,function(index,item){//文本表单的值不为空才处理if(item.value && $.trim(item.value)!=""){if(!data[item.name]){data[item.name]=item.value;}else{//name属性相同的表单,值以英文,拼接data[item.name]=data[item.name]+','+item.value;}}});//console.log(data);return data;}
</script>

页面内容请求

<script>// 加载页面内容$(function () {CoreUtil.sendAjax("/home",null, function (res) {console.log(res.data);$("h3").text(res.data);},"GET",false)});</script>

测试

拦截请求路径,并根据返回数据更新html页面,

API接口

@JwtPermissions
@GetMapping("/home")
@ResponseBody
public ResultVo home() {log.info("这是JWT请求测试");return ResultVoUtil.success("请求成功", "你好啊!!!!!,这是JWT接口");
}

查看日志可知,拦截有效

总结

后续只需要在要拦截的接口方法上面加上@JwtPermissions就可以进行拦截了(即请求头要携带token才能访问接口),当然也可以通过配置文件进行配置拦截路径,文件是JwtInterceptorConfig,通过JwtInterceptorConfig文件配置了拦截路径,而当中有些接口不需要拦截,在接口方法上面添加@IgnorePermissions注解就可以了

项目github地址是:https://github.com/lenyuqin/study-springboot/tree/master/springboot-shiro-jwt

Springboot -Shiro整合JWT(注解形式)相关推荐

  1. 【SpringBoot】44、SpringBoot中整合JWT实现Token验证(整合篇)

    什么是JWT? Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519),该 token 被设计为紧凑且安全的,特别适用于分 ...

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

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

  3. springboot+shiro+redis+jwt实现多端登录:PC端和移动端同时在线(不同终端可同时在线)

    前言 之前写了篇 springboot+shiro+redis多端登录:单点登录+移动端和PC端同时在线 的文章,但是token用的不是 jwt 而是 sessionID,虽然已经实现了区分pc端和移 ...

  4. SpringBoot(9)整合JWT实现Token验证

    一.什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以J ...

  5. springboot+shiro使用权限注解问题_无法使用注解_使用注解无法跳转无权限页面

    环境 springboot:2.5.5 shiro:1.8.0 (shiro-spring-boot-web-starter) idea 常用注解 一些小问题 1. 无法使用权限注解 实测使用shir ...

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

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

  7. 玩转 SpringBoot 2 之整合 JWT 上篇

    前言 该文主要带你了解什么是 JWT,以及JWT 定义和先关概念的介绍,并通过简单Demo 带你了解如何使用 SpringBoot 2 整合 JWT. 介绍前在这里我们来探讨一下如何学习一门新的技术, ...

  8. springboot+shiro前后端分离过程中跨域问题、sessionId问题、302鉴权失败问题

    写在前面:2020年2月29号修改该文章,之前针对302鉴权失败问题的解决方案存在 "WebUtils.toHttp 往返回response写返回值的时候出现回写跨域问题".现已进 ...

  9. 教你 Shiro + SpringBoot 整合 JWT

    本篇文章将教大家在 shiro + springBoot 的基础上整合 JWT (JSON Web Token) 如果对 shiro 如何整合 springBoot 还不了解的可以先去看我的上一篇文章 ...

最新文章

  1. 【天池赛事】零基础入门语义分割-地表建筑物识别 Task6:分割模型模型集成
  2. Linux复习资料(二)、Linux基本操作
  3. 使用IDEA 创建SpringBoot项目
  4. 表格状态列_不用软件也能做好多个项目跟进管理?我用一个协同表格就搞定
  5. Linux GCC GDB 第一节
  6. 如何把图片整合到war3的mpq文件中作为登录背景界面
  7. 在word中如何设置稿纸和字帖?学会帮你省下字帖钱哟!
  8. 论文记载:A Survey on Traffic Signal Control Methods
  9. nx零件库插件_支持Fusion 360软件的3DSource零件库插件发布
  10. 传奇源码分析-服务器端
  11. SiamRPN论文笔记
  12. 杠杆炒股亏损多少就会被平仓?
  13. Fortran NINT函数意思
  14. 重磅!Waymo首席执行官离职,自动驾驶商业化打上“问号”
  15. 课堂在线录屏:EV录屏软件配置设置
  16. 机器视觉工程师应该知道的23个工业镜头专业术语
  17. Win8.1/Win8/Win7桌面图标无法拖动怎么办
  18. JSTL 标签库c:if :forEach :forTokens
  19. python销售数据分析方法_Python数据分析之药品销售案例分析(上)
  20. 解决img标签src路径为本地路径访问受限问题:Not allowed to load local resource

热门文章

  1. 老板,我们的网站又挂了——漫谈 DDoS 攻击
  2. 扬州大学计算机控制技术课设,计算机控制技术的课设.doc
  3. Question2Answer 安全
  4. mac usb iso linux系统安装教程,Mac上制作linux系统U盘安装盘
  5. K8S使用教程(详细)
  6. 使用CainAbel进行网络嗅探
  7. Editor.md 的Markdown 编辑器集成与使用(全)
  8. php根据来路,小西的博客
  9. 【DFS专题训练】踏青 C++程序题 连通块问题
  10. 秀场精灵陈梓桐 受邀担任第六季完美童模全球总决赛首席体验官