springboot+shiro+jwt
shiro和jwt不明白的查看其他文档,本人不在这里赘述!!
1.引用 shiro和jwt
在pom.xml文件中添加引用
<!--整合Shiro安全框架--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <!--集成jwt实现toke--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.18.1</version> </dependency>
2.添加shiro配置类
package com.ckfuture.pro.config; import com.ckfuture.pro.shiro.CustomRealm; import com.ckfuture.pro.shiro.JWTFilter; import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; import org.apache.shiro.mgt.DefaultSubjectDAO; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.mgt.SecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * @descrption: shiro配置类 * @author: CKFuture * @since: 2021-09-24 16:15 * @version: v1.0 * @LastEditTime: * @LastEditors: * @copyright: hrbckfuture.com */ @Configuration public class ShiroConfig { /** * 先经过token过滤器,如果检测到请求头存在 token,则用 token 去 login,接着走 Realm 去验证 */ @Bean public ShiroFilterFactoryBean factory(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); // 添加自己的过滤器并且取名为jwt Map<String, Filter> filterMap = new LinkedHashMap<>(); //设置我们自定义的JWT过滤器 filterMap.put("jwt", new JWTFilter()); factoryBean.setFilters(filterMap); factoryBean.setSecurityManager(securityManager); // 设置无权限时跳转的 url; factoryBean.setUnauthorizedUrl("/unauthorized/无权限"); Map<String, String> filterRuleMap = new HashMap<>(); // 所有请求通过我们自己的JWT Filter filterRuleMap.put("/**", "jwt"); // 放行不需要权限认证的接口 //放行Swagger接口 filterRuleMap.put("/v2/api-docs","anon"); filterRuleMap.put("/swagger-resources/configuration/ui","anon"); filterRuleMap.put("/swagger-resources","anon"); filterRuleMap.put("/swagger-resources/configuration/security","anon"); filterRuleMap.put("/swagger-ui.html","anon"); filterRuleMap.put("/webjars/**","anon"); //放行登录接口和其他不需要权限的接口 filterRuleMap.put("/sys/userinfo/login", "anon"); filterRuleMap.put("/unauthorized/**", "anon"); factoryBean.setFilterChainDefinitionMap(filterRuleMap); return factoryBean; } /** * 注入 securityManager */ @Bean public SecurityManager securityManager(CustomRealm customRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置自定义 realm. securityManager.setRealm(customRealm); /* * 关闭shiro自带的session */ DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); return securityManager; } /** * 添加注解支持 */ @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 强制使用cglib,防止重复代理和可能引起代理出错的问题 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } }
3.自定义过滤器,对token进行处理
package com.ckfuture.pro.shiro; import com.ckfuture.pro.shiro.JWTToken; import com.ckfuture.util.ResponseCode; import com.ckfuture.util.ResponseMsg; import com.ckfuture.util.Result; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; /** * @descrption: 自定义过滤器,对token进行处理 * @author: CKFuture * @since: 2021-09-24 14:47 * @version: v1.0 * @LastEditTime: * @LastEditors: * @copyright: hrbckfuture.com */ public class JWTFilter extends BasicHttpAuthenticationFilter { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 如果带有 token,则对 token 进行检查,否则直接通过 */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws UnauthorizedException { //判断请求的请求头是否带上 "token" if (isLoginAttempt(request, response)) { //如果存在,则进入 executeLogin 方法执行登入,检查 token 是否正确 try { executeLogin(request, response); return true; } catch (Exception e) { //token 错误 responseError(response, e.getMessage()); } } //如果请求头不存在 token,则可能是执行登陆操作或者是游客状态访问,无需检查 token,直接返回 true return true; } /** * 判断用户是否想要登入。authorization : token 数值 * 检测 header 里面是否包含 token 字段 */ @Override protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { try { HttpServletRequest req = (HttpServletRequest) request; String token = req.getHeader("authorization").replace("token","").trim(); return token != null; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 执行登陆操作 */ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("authorization").replace("token","").trim(); JWTToken jwtToken = new JWTToken(token); // 提交给realm进行登入,如果错误它会抛出异常并被捕获 getSubject(request, response).login(jwtToken); // 如果没有抛出异常则代表登入成功,返回true return true; } /** * 对跨域提供支持 */ @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态 if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpServletResponse.setStatus(HttpStatus.OK.value()); return false; } return super.preHandle(request, response); } /** * 将非法请求跳转到 /unauthorized/** */ private void responseError(ServletResponse response, String message) { try { HttpServletResponse httpServletResponse = (HttpServletResponse) response; //设置编码,否则中文字符在重定向时会变为空字符串 message = URLEncoder.encode(message, "UTF-8"); httpServletResponse.sendRedirect("/unauthorized/error/"+message); } catch (IOException e) { logger.error(e.getMessage()); } } }
4.TOKEN扩展类
package com.ckfuture.pro.shiro; import org.apache.shiro.authc.AuthenticationToken; /** * @descrption: TOKEN扩展类 * @author: CKFuture * @since: 2021-09-24 16:16 * @version: v1.0 * @LastEditTime: * @LastEditors: * @copyright: hrbckfuture.com */ public class JWTToken implements AuthenticationToken { private String token; public JWTToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } }
5.JWT工具类
package com.ckfuture.pro.shiro; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; import javax.servlet.http.HttpServletRequest; import java.io.UnsupportedEncodingException; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; /** * @descrption: JWT工具类 * @author: CKFuture * @since: 2021-09-24 16:17 * @version: v1.0 * @LastEditTime: * @LastEditors: * @copyright: hrbckfuture.com */ public class JWTUtil { // 过期时间 24 小时 60 * 24 * 60 * 1000 private static final long EXPIRE_TIME = 15 * 60 * 1000;//15分钟 // 密钥 private static final String SECRET = "hrbckfuture"; private static String json=""; /** * 生成 token */ public static String createToken(String userId) { try { // 设置过期时间 Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); // 私钥和加密算法 Algorithm algorithm = Algorithm.HMAC256(SECRET); // 设置头部信息 Map<String, Object> header = new HashMap<>(2); header.put("Type", "Jwt"); header.put("alg", "HS256"); // 返回token字符串 附带userId信息 return JWT.create() .withHeader(header) .withClaim("userId", userId) //到期时间 .withExpiresAt(date) //创建一个新的JWT,并使用给定的算法进行标记 .sign(algorithm); } catch (Exception e) { return null; } } /** * 校验 token 是否正确 */ public static boolean verify(String token, String userId) { try { Algorithm algorithm = Algorithm.HMAC256(SECRET); //在token中附带了username信息 JWTVerifier verifier = JWT.require(algorithm) .withClaim("userId", userId) .build(); //验证 token verifier.verify(token); return true; } catch (Exception exception) { return false; } } /** * 获得token中的信息,无需secret解密也能获得 */ public static String getUserId(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("userId").asString(); } catch (JWTDecodeException e) { return null; } } /** * 获取请求头中的 token * @param request * @return */ public static String get_token(HttpServletRequest request) { try{ Enumeration<String> headerNames = request.getHeaderNames(); //根据名称获取请求头的值 while(headerNames.hasMoreElements()){ String name = headerNames.nextElement(); if(name.equals("authorization")){ json=request.getHeader(name).replace("token","").trim(); } } return json; }catch (Exception ex){ System.out.println(ex.getMessage()); return ""; } } /** 获取精确到毫秒的时间戳 * @param date * @return **/ public static Long getTimestamp(Date date) { if (null == date) { return (long) 0; } String timestamp = String.valueOf(date.getTime()); return Long.valueOf(timestamp); } }
6.自定义Realm,实现Shiro安全认证
package com.ckfuture.pro.shiro; import com.ckfuture.pro.shiro.JWTToken; import com.ckfuture.pro.shiro.JWTUtil; import com.ckfuture.pro.sys.userinfo.service.userinfoService; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @descrption: 自定义Realm,实现Shiro安全认证 * @author: CKFuture * @since: 2021-09-24 16:18 * @version: v1.0 * @LastEditTime: * @LastEditors: * @copyright: hrbckfuture.com */ @Component public class CustomRealm extends AuthorizingRealm { @Autowired private userinfoService userService;//用户信息接口 /** * 必须重写此方法,不然会报错 */ @Override public boolean supports(AuthenticationToken token) { return token instanceof JWTToken; } /** * 默认使用此方法进行用户信息正确与否验证,错误抛出异常即可。 * 自定义后续方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String token = (String) authenticationToken.getCredentials(); // 解密获得userId,用于和数据库进行对比 String userId = JWTUtil.getUserId(token); if (userId == null || !JWTUtil.verify(token, userId)) { throw new AuthenticationException("token认证失败!"); } /* 以下数据库查询可根据实际情况,可以不必再次查询,这里我两次查询会很耗资源 * 具体业务判断 */ // String password = userService.getPassword(username); // if (password == null) { // throw new AuthenticationException("该用户不存在!"); // } // int ban = userService.checkUserBanStatus(username); // if (ban == 1) { // throw new AuthenticationException("该用户已被封号!"); // } return new SimpleAuthenticationInfo(token, token, "MyRealm"); } /** * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("————权限认证————"); String userId = JWTUtil.getUserId(principals.toString()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //获得该用户角色 //String role = userService.getRole(username); //每个角色拥有默认的权限 // String rolePermission = userService.getRolePermission(username); //每个用户可以设置新的权限 // String permission = userService.getPermission(username); // Set<String> roleSet = new HashSet<>(); // Set<String> permissionSet = new HashSet<>(); //需要将 role, permission 封装到 Set 作为 info.setRoles(), info.setStringPermissions() 的参数 // roleSet.add(role); // permissionSet.add(rolePermission); // permissionSet.add(permission); //设置该用户拥有的角色和权限 // info.setRoles(roleSet); // info.setStringPermissions(permissionSet); return info; } }
7.获取token签名
在用户登录接口中返回token
@ApiOperation(value = "用户登录",httpMethod = "GET", notes = "获取用户登录信息") @ResponseBody @RequestMapping(value = {"/login"}, method = RequestMethod.GET) public Result login( @ApiParam(value = "登录账号", required = true) String jobNO ,@ApiParam(value = "登录密码", required = true) String upassword) { try { //返回用户实体 entity = service.getOneByJobNOandPassWord(jobNO, upassword); //生成签名 String userId=entity.getId().toString(); String user_token= JWTUtil.createToken(userId); //返回token entity.setToken(user_token); return Result.success(entity); } catch (Exception e) { System.out.println(e.getMessage()); return Result.failure(ResponseCode.ERROR_999, ResponseMsg.QUERY_ERROR); } }
其他接口正常写就可以,在过滤器中就有token和权限的验证。
其他请求头中携带 token
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!