JWT
JWT文章:JWT详解_baobao555#的博客-CSDN博客_jwt
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用 JWT 在用户和服务器之间传递安全可靠的信息。
在本项目中,我们规定每次请求时,需要在请求头中带上 token ,通过 token 检验权限,如没有,则说明当前为未登录状态
Shiro
Shiro - Shiro简介;Shiro与Spring Security区别;Spring Boot集成Shiro_MinggeQingchun的博客-CSDN博客_shiro
1,登录
/** * 登陆 * * @param username 用户名 * @param password 密码 */ @RequestMapping(value = "/login", method = RequestMethod.POST) public ResultMap login(String username, String password) { String realPassword = userMapper.getPassword(username); if (realPassword == null) { return resultMap.fail().code(401).message("用户名错误"); } else if (!realPassword.equals(password)) { return resultMap.fail().code(401).message("密码错误"); } else { //账号信息认证通过后调用JWT工具类创建Token并返回给前端 return resultMap.success().code(200).message(JWTUtil.createToken(username)); } }
2,ShiroConfig配置
/** * 先走 filter ,然后 filter 如果检测到请求头存在 token,则用 token 去 Shiro,走 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"); // 访问 /unauthorized/**,/login,不通过JWTFilter filterRuleMap.put("/unauthorized/**", "anon"); filterRuleMap.put("/login", "anon"); factoryBean.setFilterChainDefinitionMap(filterRuleMap); return factoryBean; } /** * 自定义身份认证 realm,注入 securityManager */ @Bean public SecurityManager securityManager(CustomRealm customRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 设置realm. securityManager.setRealm(customRealm); DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); return securityManager; }
3,CustomRealm配置
@Component public class CustomRealm extends AuthorizingRealm { @Autowired private UserMapper userMapper; /** * 必须重写此方法,不然会报错 */ @Override public boolean supports(AuthenticationToken token) { return token instanceof JWTToken; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("————权限认证————"); String username = JWTUtil.getUsername(principals.toString()); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //获得该用户角色 String role = userMapper.getRole(username); //每个角色拥有默认的权限 String rolePermission = userMapper.getRolePermission(username); //每个用户可以设置新的权限 String permission = userMapper.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; }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("————身份认证方法————"); String token = (String)authenticationToken.getCredentials(); String userName= JWTUtil.getUsername(token); if(userName==null || !JWTUtil.verify(token, userName)){ throw new AuthenticationException("token认证失败!"); } // 从数据库获取对应用户名密码的用户 //String password =getPassword(token.getUsername()); String password =userMapper.getPassword(userName); if (null == password) { throw new AuthenticationException("该用户不存在!"); } int ban =userMapper.checkUserBanStatus(userName); if (ban == 1) { throw new AuthenticationException("该用户已被封号!"); } return new SimpleAuthenticationInfo(token, token, "MyRealm"); }
4,JWT过滤器配置
/** * 判断用户是否想要登入。 * 检测 header 里面是否包含 Token 字段 */ @Override protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { HttpServletRequest req = (HttpServletRequest) request; String token = req.getHeader("Token"); return token != null; }
/** * 如果带有 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 错误 e.printStackTrace(); //responseError(response, e.getMessage()); } } //没有token无法通过过滤器。如果是登录接口应该在ShiroConfig那块放行不经过过滤器 return false; }
/** * 执行登陆操作,即执行CustomRealmdo.GetAuthenticationInfo()身份认证权限 */ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("Token"); JWTToken jwtToken = new JWTToken(token); // 提交给realm进行登入,如果错误他会抛出异常并被捕获 getSubject(request, response).login(jwtToken); // 如果没有抛出异常则代表登入成功,返回true return true; }
5,权限配置
角色权限注解:@RequiresRoles 权限配置注解:@RequiresPermissions
经过JWTFilter过滤器和用户身份校验后就进入CustomRealm的权限校验模块
/** * 拥有 vip 权限可以访问该页面 */ @GetMapping("/getVipMessage") @RequiresRoles(logical = Logical.OR, value = {"admin"}) @RequiresPermissions("vip") public ResultMap getVipMessage() { try { return resultMap.success().code(200).message("成功获得 vip 信息!"); }catch (Exception e) { e.printStackTrace(); } return resultMap.success().code(201).message("获得 vip 信息失败!"); }