spring security
The traditional spring security permission structure involves five tables
-User table
-Role table
-User role table
-System resource table
-Role resource table
Assign different roles to users to dynamically load the permission menu under this role.
New demand
The menu permissions are not allocated according to the role, but after the user registers, the user checks the required permission menu (different payment is made according to different menu choices), that is, the user selects which menu permissions to open, which weakens the concept of user role.
Therefore, sprIng security is modified
Design three tables
-User table (record registered user information)
-System menu resource table (record all menu information)
- user resource table (record the menu permissions selected by the user, which can be updated)
Design idea and code
After the user registers, only the default menus can be operated, and there is no operation permission for the menu that has not been opened.
Therefore, after the user is registered, the required permissions should be opened first.
@Data public class AddUserPermissionVO { @ApiModelProperty(value = "user ID") private String userId; @ApiModelProperty(value = "User selected permission list", notes = "It's the path address list") private List<String> permissionLists; }
If the menu permission is opened for the first time, add the user resource table. If you want to open other menu permissions later, update it. Encryption algorithm is used to encrypt and decrypt tokenId.
/** * Add user menu permissions * @param param */ public void addUserPermission(AddUserPermissionVO param) { SysUserPermission model = new SysUserPermission(); List<String> permissionLists = param.getPermissionLists(); // Put users id Also encapsulated in token permissionLists.add(param.getUserId()); Map<String, Object> payload = new HashMap<>(); // Encapsulate the user authority menu into key payload.put(SIGNING_KEY, permissionLists); String token = JwtUtil.createToken(payload, EXP); model.setId(param.getUserId()); model.setUserPermission(token); SysUserPermission user = sysUserPermissionService.findById(param.getUserId()); if (CommonUtil.isEmpty(user)) { sysUserPermissionService.add(model); } else { sysUserPermissionService.update(model); } }
After adding user permissions, each time the user logs in, the front end will return the tokenId in the user resource table. The front end will dynamically load the user operable menu according to the resource menu path information in the returned tokenId.
SysUserPermission sysUserPermission = sysUserPermissionService.findById(oper.getOperatorId()); String tokenId = sysUserPermission.getUserPermission(); LoginResp loginResp = new LoginResp(); // take tokenId Return to web Page for subsequent permission comparison loginResp.setTokenId(tokenId); return loginResp;
By doing this, the front end can dynamically display the list of operable permission menus that users can see. However, permission control is not achieved (i.e. the front-end is untrusted and capricious), and users can operate if they know the path of other menus.
In spring security, add role annotations on the back-end method interface to control the interface operation according to the user's role.
Therefore, using AOP crosscutting filtering, sprIng security is transformed again, and the user menu permission is controlled in the form of annotation.
Front end untrusted, back-end permission control transformation
/** * Permission custom annotation * * @author LH * @date 2022/1/11 11:26 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface UserPermission
{ String value() default ""; }
AOP crosscutting filtering
@Component @Aspect public class PermissionFilter { // Custom encryption key SIGNING_KEY private static final String SIGNING_KEY = "Your encryption key(custom)"; /** * Define tangent point * @annotation Take the annotation as the tangent point, and the parameter defines the path for the annotation */ @Pointcut("@annotation(com.xxx.security.annotation.UserPermission)") private void permissionAspect() { } /** * @Before Method is verified before execution. permissionAspect() is the pointcut, @ annotation(userPermission) is the annotation parameter, and point is the pointcut object * @param userPermission * @return */ @Before(value = "permissionAspect()&&@annotation(userPermission)") public Object checkPermissionkey(UserPermission userPermission) { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); // obtain tokenId And compare String tokenId = request.getHeader("X-Token"); if (CommonUtil.isEmpty(tokenId)) { // There is also a layer of judgment here. When a user who is not logged in operates, there is no tokenId,Set a default default tokenId throw new RuntimeException("User has no access rights!"); } JwtUtil.parseToken(tokenId); // Here, the path prefix is removed and the subsequent request path is matched String str = StrUtil.removeAll(JwtUtil.parseToken(tokenId).get(SIGNING_KEY).toString(), '[', ']'); List<String> keyLists = BwFunc.getListSplitStr(str, ","); // userPermission.value()Are annotation parameters if (keyLists.contains(userPermission.value())) { return null; } else { throw new RuntimeException("User has no access rights!"); } } }
Add a custom annotation on the interface method for testing
@RequestMapping(value = "/addUser", method = RequestMethod.POST) @ApiOperation(value = "User add", notes = "User add", httpMethod = "POST") @UserPermission("addUser") // Only when carried by the user token It's carrying addUser Parameter before subsequent operations can be performed on the interface method. Otherwise, the returned user has no operation permission public BwResult addUserPermission(@RequestBody AddUserVO param) { sysUserPermissionAppService.addUser(param); return BwResult.success("User added successfully!"); }
At this point, the simple demo transformation is completed. We'll see whether it can be optimized later.