Springboot -Shiro整合JWT(注解形式)
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(注解形式)相关推荐
- 【SpringBoot】44、SpringBoot中整合JWT实现Token验证(整合篇)
什么是JWT? Json web token (JWT),是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准((RFC 7519),该 token 被设计为紧凑且安全的,特别适用于分 ...
- java shiro jwt_Springboot实现Shiro整合JWT的示例代码
写在前面 之前想尝试把JWT和Shiro结合到一起,但是在网上查了些博客,也没太有看懂,所以就自己重新研究了一下Shiro的工作机制,然后自己想了个(傻逼)办法把JWT和Shiro整合到一起了 另外接 ...
- springboot+shiro+redis+jwt实现多端登录:PC端和移动端同时在线(不同终端可同时在线)
前言 之前写了篇 springboot+shiro+redis多端登录:单点登录+移动端和PC端同时在线 的文章,但是token用的不是 jwt 而是 sessionID,虽然已经实现了区分pc端和移 ...
- SpringBoot(9)整合JWT实现Token验证
一.什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).定义了一种简洁的,自包含的方法用于通信双方之间以J ...
- springboot+shiro使用权限注解问题_无法使用注解_使用注解无法跳转无权限页面
环境 springboot:2.5.5 shiro:1.8.0 (shiro-spring-boot-web-starter) idea 常用注解 一些小问题 1. 无法使用权限注解 实测使用shir ...
- Shiro整合JWT:解决jwt注销和续签的问题
文章目录 1. 场景一:token的注销问题(黑名单) 2. 场景二:token的续签问题 3. 项目中的实现 3.1 封装JWT工具类 3.2 配置Shiro的自定义认证类 3.3 登录和退出登录( ...
- 玩转 SpringBoot 2 之整合 JWT 上篇
前言 该文主要带你了解什么是 JWT,以及JWT 定义和先关概念的介绍,并通过简单Demo 带你了解如何使用 SpringBoot 2 整合 JWT. 介绍前在这里我们来探讨一下如何学习一门新的技术, ...
- springboot+shiro前后端分离过程中跨域问题、sessionId问题、302鉴权失败问题
写在前面:2020年2月29号修改该文章,之前针对302鉴权失败问题的解决方案存在 "WebUtils.toHttp 往返回response写返回值的时候出现回写跨域问题".现已进 ...
- 教你 Shiro + SpringBoot 整合 JWT
本篇文章将教大家在 shiro + springBoot 的基础上整合 JWT (JSON Web Token) 如果对 shiro 如何整合 springBoot 还不了解的可以先去看我的上一篇文章 ...
最新文章
- 【天池赛事】零基础入门语义分割-地表建筑物识别 Task6:分割模型模型集成
- Linux复习资料(二)、Linux基本操作
- 使用IDEA 创建SpringBoot项目
- 表格状态列_不用软件也能做好多个项目跟进管理?我用一个协同表格就搞定
- Linux GCC GDB 第一节
- 如何把图片整合到war3的mpq文件中作为登录背景界面
- 在word中如何设置稿纸和字帖?学会帮你省下字帖钱哟!
- 论文记载:A Survey on Traffic Signal Control Methods
- nx零件库插件_支持Fusion 360软件的3DSource零件库插件发布
- 传奇源码分析-服务器端
- SiamRPN论文笔记
- 杠杆炒股亏损多少就会被平仓?
- Fortran NINT函数意思
- 重磅!Waymo首席执行官离职,自动驾驶商业化打上“问号”
- 课堂在线录屏:EV录屏软件配置设置
- 机器视觉工程师应该知道的23个工业镜头专业术语
- Win8.1/Win8/Win7桌面图标无法拖动怎么办
- JSTL 标签库c:if :forEach :forTokens
- python销售数据分析方法_Python数据分析之药品销售案例分析(上)
- 解决img标签src路径为本地路径访问受限问题:Not allowed to load local resource