# jwtAuthorization **Repository Path**: jefferyeven/jwt-authorization ## Basic Information - **Project Name**: jwtAuthorization - **Description**: jwt+自定义权限认证,支持动态权限 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-04-18 - **Last Updated**: 2022-05-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 简介 spring boot 有很多鉴权的框架例如shiro,security等,但是这些鉴权工具是基于session进行鉴权,当我们使用jwt的时候很多的配置就十分的冗余,并且也会占用更多的内存资源与拖慢程序的速度。因此我重新对权限鉴权进行重构。由于我的业务比较简单,这里我采用用了ACL访问控制列表权限模型进行设计。 # 思路 ## 整体架构 因为jwt已经帮我们做了认证的的事情,所以在这里我把认证的这件事情看作鉴权的一部分。在这里我们只有鉴权。而认证(判断是否登录)只是鉴权的一个模块。所以这个模块的一个核心就是鉴权器。 ![image.png](./img/3.png) ## 鉴权器链 鉴权器链,这里是使用责任链模式,由一个基础的鉴权器,和多个其他的鉴权器组成。当一个鉴权器鉴权成功,我们就认为该用户,有该url的权限。 ![image.png](./img/2.png) 其中其他鉴权器,是由用户自己实现的,默认的话只有基础鉴权器和注解鉴权器。 ## 基础鉴权器 基础鉴权器,根据config有几种鉴权策略,这里我主要使用了策略模式来替代switch case的编写。 ![image.png](./img/1.png) 其中策略选择器要做的事情是把最详细的url匹配的验证策略选择出来; 例子 : 策略选择器 :{"/*",HaveToken,"test/*",AnyAuthority,"test/hello",DenyAll} 当一个url来到时,策略选择器,会将其转换为,一组匹配string 例子:url = "test/helloWorld" ->["test/helloWorld","test/*","/*"]; 然后进行匹配,这里也是一个责任链的模式,越详细的先匹配,匹配出来就停止匹配,都没有匹配,就抛出没有匹配错误。 ## 注解鉴权器 ### 获取注解 鉴权是基于web访问的url,所以我们只需要查看所有有(Controller)注解的类,和类的方法是否有NeedAuthority,的注解 如果类有needAuthority注解: 有的话我们就把类上的requestMapping的value加上模糊匹配符,然后把值方在requestmap上 如果方法上有needAuthority注解: 有的话我们就把类上的requestMapping的value,和方法上的各种mapping的value,拼接成一个具体的url,把具体的url,分别放在各种请求类型(post/get/delete/put/request)的urlMap上。 ### ![image.png](./img/4.png) **这里再说一下AnyAuthority,AllAuthority,和HaveToken** 这里需要从requset的头部文件取出token,并且验证该token,从token中取出权限,对比已经匹配出来的url所需的权限。 # 使用方式 继承JwtSecurityConfigAdapter。 ## 基础使用 ### 配置config类,配置访问链接和需要的权限 设置基础鉴权,设置鉴权的url,和设置鉴权方式。 addConfigUrlsPermission("/admin/*").haveAnyAuthority("admin"); ### 使用注解,配置url访问的权限 1. 在config类开启注解鉴权 setUseAnnoation(true); 2. 使用@NeedAuthorize注解 ``` @NeedAuthorize(authorizeLevel = PermissionLevel.HAVE_ANY_AUTHORITY,authorties = {"admin"}) @GetMapping("/tes") public String test(){ } ``` ## 进阶内容 1. 自定义jwt,自定义签名和验证jwt 创建自定义tokenUtil类 继承implements TokenVerifyer ```java public class TokenUtil implements TokenVerifyer { } ``` 2. 设置自定义的鉴权器和他的次序 addAuthorization(dynamicAuthorization,1); ```java @Configuration public class JwtSecurityConfig extends JwtSecurityConfigAdapter { public boolean haveSqlUpdate = true; public final String[] excludeUrls = {"/login","/register","/doLogin","/test/hello"}; @Autowired DynamicAuthorization dynamicAuthorization; @Override public void config(AuthorizationConfig authorizationConfig){ // 添加自定义authorization authorizationConfig.addAuthorization(dynamicAuthorization,1); // 开启注解鉴权 authorizationConfig.setUseAnnoation(true); } } ``` ## 注意 **动态权限** 只有在jwtSecurityConfig.haveSqlUpdate为真时才查询数据库。 如果你是想要动态权限,在更新数据库的时候设置jwtSecurityConfig.haveSqlUpdate = true;代表你已经对数据库更改,系统需要重新导入数据 # example 自定义TokenUtil ```java package com.example.springbootsecurityjwtdemo.security; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.example.springbootsecurityjwtdemo.security.authentization_strategy.AuthorizationStrategyManger; import com.example.springbootsecurityjwtdemo.security.exception.JwtResponseMag; import com.example.springbootsecurityjwtdemo.security.exception.JwtSecurityException; import com.example.springbootsecurityjwtdemo.security.utils.TokenVerifyer; import com.example.springbootsecurityjwtdemo.security.utils.VerifyTokenResult; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; import java.util.List; @Component public class MyTokenUtil implements TokenVerifyer { public String salt = "fdelsfjdo.243"; public long expireTime = 100000000; String isser = "test"; private String tokenName ="token"; private JWTVerifier verifier = JWT.require(Algorithm.HMAC256(salt)).withIssuer(isser).build(); @Override public VerifyTokenResult verifyToken(HttpServletRequest request, HttpServletResponse response) { VerifyTokenResult verifyTokenResult = new VerifyTokenResult(); String token = request.getHeader(tokenName); if(token==null){ throw new JwtSecurityException(JwtResponseMag.NoTokenError); } try { DecodedJWT jwt = verifier.verify(token); verifyTokenResult.setPassVerify(true); Claim authoritiesClaim = jwt.getClaims().get("authorities"); if(authoritiesClaim==null){ return verifyTokenResult; } List authorities = JSONObject.parseArray(authoritiesClaim.asString(),String.class); verifyTokenResult.setAuthorities(authorities); } catch (Exception e){ verifyTokenResult.setPassVerify(false); } return verifyTokenResult; } String sign(String name, List authorities){ String token = null; try { Date expiresAt = new Date(System.currentTimeMillis() + expireTime); token = JWT.create() .withIssuer(isser) .withClaim("username", name) .withClaim("authorities", JSON.toJSONString(authorities)) .withExpiresAt(expiresAt) // 使用了HMAC256加密算法。 .sign(Algorithm.HMAC256(salt)); } catch (Exception e){ e.printStackTrace(); } return token; } } ``` 配置类 ```java package com.example.springbootsecurityjwtdemo.security; import com.example.springbootsecurityjwtdemo.security.config.AuthorizationConfig; import com.example.springbootsecurityjwtdemo.security.config.HttpConfig; import com.example.springbootsecurityjwtdemo.security.config.JwtSecurityConfigAdapter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @Configuration public class JwtSecurityConfig extends JwtSecurityConfigAdapter { public boolean haveSqlUpdate = true; public final String[] excludeUrls = {"/login","/register","/doLogin","/test/hello"}; @Autowired DynamicAuthorization dynamicAuthorization; @Autowired MyTokenUtil myTokenUtil; /** * 因为我们的验证是交与jwt来做,所有我们只需要鉴权 */ @Override public void config(HttpConfig httpConfig){ //更改默认的鉴权策略 httpConfig.getDefaultUrlConfig().haveToken(); // 配置基础需要鉴权的url // 所有允许的url httpConfig.addConfigUrlsPermission(excludeUrls).permitAll(); httpConfig.addConfigUrlsPermission("/user/*").haveAnyAuthority("user","admin"); httpConfig.addConfigUrlsPermission("/admin/*").haveAnyAuthority("admin"); httpConfig.setHeaderParameterTokenName("token"); } @Override public void config(AuthorizationConfig authorizationConfig){ // 添加自定义authorization authorizationConfig.addAuthorization(dynamicAuthorization,1); // 开启注解鉴权 authorizationConfig.setUseAnnoation(true); authorizationConfig.setStrategyTokenVerifyer(myTokenUtil); } } ``` 自定义动态鉴权 ```java package com.example.springbootsecurityjwtdemo.security; import com.example.springbootsecurityjwtdemo.bean.dto.AuthoritiesPermissionDto; import com.example.springbootsecurityjwtdemo.mapper.AuthoritiesPermissionMapper; import com.example.springbootsecurityjwtdemo.security.JwtSecurityConfig; import com.example.springbootsecurityjwtdemo.security.authorization.AbstractAuthorization; import com.example.springbootsecurityjwtdemo.security.authentization_strategy.AuthorizationStrategyManger; import com.example.springbootsecurityjwtdemo.security.bean.AuthorizationState; import com.example.springbootsecurityjwtdemo.security.utils.VerifyTokenResult; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.*; @Component public class DynamicAuthorization extends AbstractAuthorization { @Autowired JwtSecurityConfig jwtSecurityConfig; @Autowired AuthoritiesPermissionMapper authoritiesPermissionMapper; private final String noMatch = "don't find match url"; Map> urlAuthoritiesMap = new HashMap<>(); @Override public AuthorizationState passAuthenizate(HttpServletRequest request, HttpServletResponse response) throws Exception { if(jwtSecurityConfig.haveSqlUpdate){ updateUrlPermissionMap(); } String findUrl = findMathUrl(request.getRequestURI()); if (noMatch.equals(findUrl)) { return AuthorizationState.UnAuthenizateState; } Set needAuthority = urlAuthoritiesMap.get(findUrl); VerifyTokenResult verifyTokenResult = AuthorizationStrategyManger.getStrategyTokenVerifyer().verifyToken(request,response); if(!verifyTokenResult.isPassVerify()){ return AuthorizationState.NoPassState; } List authorities = verifyTokenResult.getAuthorities(); for(String authority:authorities){ if(needAuthority.contains(authority)){ return AuthorizationState.PassState; } } return AuthorizationState.NoPassState; } public void updateUrlPermissionMap(){ urlAuthoritiesMap.clear(); if(jwtSecurityConfig.haveSqlUpdate){ List authoritiesPermissionDtoList = authoritiesPermissionMapper.selectAllAuthoritiesPermissionDto(); for(AuthoritiesPermissionDto authoritiesPermissionDto:authoritiesPermissionDtoList){ String url = authoritiesPermissionDto.getUrl(); String name = authoritiesPermissionDto.getName(); Set urlAuthorities = urlAuthoritiesMap.getOrDefault(url,new HashSet<>()); urlAuthorities.add(name); urlAuthoritiesMap.put(url,urlAuthorities); } } jwtSecurityConfig.haveSqlUpdate = false; } private String findMathUrl(String url) { if (urlAuthoritiesMap.containsKey(url)) { return url; } String[] mathes = url.split("/"); for (int i = mathes.length - 1; i >= 0; i--) { StringBuilder res = new StringBuilder(); for (int j = 0; j < i; j++) { res.append(mathes[j]).append("/"); } res.append("*"); if (urlAuthoritiesMap.containsKey(res.toString())) { return res.toString(); } } return noMatch; } } ``` # gitee https://gitee.com/jefferyeven/jwt-authorization.git