An overview of the

In the previous several articles, we have been implementing the authentication function module in the SpringCloud system to verify the identity of the current login user. In this article we’ll look at authorization in the SpringCloud architecture and verify that you can access certain features.

Certificate authority

Many students confuse authentication and authorization and treat them as the same concept. They are two completely different concepts. Here’s an easy example:

You are Zhang SAN, the moderator of a well-known forum. When you log in to the forum, enter your account password and log in successfully, which proves that you are a fool. This process is called authentication. After logging in, the system determines that you are the moderator, and you can highlight and top posts published by others, a verification process known as authorization.

In short, the certification process tells you who you are, and the authorization process tells you what you can do?

Authorization is generally implemented in the SpringCloud architecture in two ways:

  • Based on the path matcher authorization system, all requests will pass through the Springcloud Gateway Gateway. After receiving the request, the Gateway determines whether the current user has the permission to access the pathReactiveAuthorizationManager#check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext)Method for verification. This approach is primarily user-basedPath of owned resourcesThink about it.
  • Based on the method of using this method to intercept the gateway layer not to intercept, on the need for permission check method combined with SpringSecurity annotations, judge whether the current user has permission to access this method, of course, you can also use the custom annotation or using AOP to intercept calibration, the implementation ways we called based on intercept method. This approach generally takes into account the identity of the resource owned by the user.

Next, we implement the SpringCloud authorization process in two different ways.

Core code implementation

Either way, we need to know the role resources owned by the current user, so we first use the RBAC model to establish a simple user, role, resource table structure and establish the corresponding Service, Dao layer in the project.

(Resource table set up resource identification and request path two fields, easy to realize the code logic)

Path-matcher based authorization

  • Modify the custom UserDetailService remember our original UserDetailService from the definition, inloadUserByUsername()Method to return the UserDetails object. Instead of returning the fixed ‘ADMIN’ role, get the real role from the database and put the resources corresponding to the role into the UserDetails object.
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
	// Obtain a local user
	SysUser sysUser = sysUserMapper.selectByUserName(userName);
	if(sysUser ! =null) {// Get all the roles of the current user
		List<SysRole> roleList = sysRoleService.listRolesByUserId(sysUser.getId());
		sysUser.setRoles(roleList.stream().map(SysRole::getRoleCode).collect(Collectors.toList()));
		List<Integer> roleIds = roleList.stream().map(SysRole::getId).collect(Collectors.toList());
		// Obtain permissions for all roles
		List<SysPermission> permissionList = sysPermissionService.listPermissionsByRoles(roleIds);
		sysUser.setPermissions(permissionList.stream().map(SysPermission::getUrl).collect(Collectors.toList()));
		// The user who built oAuth2
		return buildUserDetails(sysUser);

	}else{
		throw  new UsernameNotFoundException("User ["+userName+"] Does not exist"); }}/** * Build the oAuth2 user and assign roles and permissions to the user with roles prefixed with ROLE_ *@paramSysUser System user *@return UserDetails
 */
private UserDetails buildUserDetails(SysUser sysUser) {
	Set<String> authSet = new HashSet<>();
	List<String> roles = sysUser.getRoles();
	if(! CollectionUtils.isEmpty(roles)){ roles.forEach(item -> authSet.add(CloudConstant.ROLE_PREFIX + item)); authSet.addAll(sysUser.getPermissions()); } List<GrantedAuthority> authorityList = AuthorityUtils.createAuthorityList(authSet.toArray(new String[0]));

	return new User(
			sysUser.getUsername(),
			sysUser.getPassword(),
			authorityList
	);
}
Copy the code

Note that SysPermission::getUrl is placed in the user’s permissions.

  • Modify AccessManager to implement permission judgment
@Autowired
private AccessManager accessManager;
@Bean
SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception{... http .httpBasic().disable() .csrf().disable() .authorizeExchange() .pathMatchers(HttpMethod.OPTIONS).permitAll() .anyExchange().access(accessManager) ...return http.build();
}
Copy the code

In original gateway configuration we injected judging custom ReactiveAuthorizationManager for permissions, we need to implement according to the request path and the user owns the resource path of judgment, if there are corresponding resource access path forward continues to back-end services, responsible for the return to the “no access”.

@Slf4j
@Component
public class AccessManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    private Set<String> permitAll = new ConcurrentHashSet<>();
    private static final AntPathMatcher antPathMatcher = new AntPathMatcher();


    public AccessManager (a){
        permitAll.add("/");
        permitAll.add("/error");
        permitAll.add("/favicon.ico");
        // If the production environment has Swagger debugging enabled
        permitAll.add("/**/v2/api-docs/**");
        permitAll.add("/**/swagger-resources/**");
        permitAll.add("/webjars/**");
        permitAll.add("/doc.html");
        permitAll.add("/swagger-ui.html");
        permitAll.add("/**/oauth/**");
    }

    /** * implement permission verification judgment */
    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> authenticationMono, AuthorizationContext authorizationContext) {
        ServerWebExchange exchange = authorizationContext.getExchange();
        // Request resources
        String requestPath = exchange.getRequest().getURI().getPath();
        // Whether to directly release
        if (permitAll(requestPath)) {
            return Mono.just(new AuthorizationDecision(true));
        }

        return authenticationMono.map(auth -> {
            return new AuthorizationDecision(checkAuthorities(auth, requestPath));
        }).defaultIfEmpty(new AuthorizationDecision(false));

    }

    /** * Check whether the resource is static *@paramRequestPath specifies the requestPath *@return* /
    private boolean permitAll(String requestPath) {
        return permitAll.stream()
                .filter(r -> antPathMatcher.match(r, requestPath)).findFirst().isPresent();
    }


    /** * Check permission *@author javadaily
     * @date2020/8/4 yet *@paramAuth user permission *@paramRequestPath specifies the requestPath *@return* /
    private boolean checkAuthorities(Authentication auth, String requestPath) {
        if(auth instanceof OAuth2Authentication){
            Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();

            returnauthorities.stream() .map(GrantedAuthority::getAuthority) .filter(item -> ! item.startsWith(CloudConstant.ROLE_PREFIX)) .anyMatch(permission -> antPathMatcher.match(permission, requestPath)); }return false; }}Copy the code
  • test

View all permissions of the current userRequest a resource within normal permissionsAccess resources that do not have permissions

Method based interception implementation

The method-based interception implementation in this article is based on the SpringSecurity built-in tag @preauthorize and is then accomplished by implementing a custom validation method hasPrivilege(). Again, there are many ways to implement it, and it is not necessary to adopt the implementation of this article.

The code logic for this approach needs to be written in the resource server, the back-end service that provides the specific business service. Because each back-end service needs to add these codes, it is recommended to extract the common starter module, and each resource server can reference the starter module.

  • Change UserDetailService The change process is the same as the previous one, except that the resource id needs to be added to the user’s rights.
sysUser.setPermissions(
		permissionList.stream()
			.map(SysPermission::getPermission)
			.collect(Collectors.toList())
);
Copy the code
  • Delete gateway interception configuration Since gateway interception is not required, we need to remove the verification logic in AccessManager and return true for all.
  • Custom method validation logic
/** * Custom permission verification *@author http://www.javadaily.cn
 */
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
    private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
    public CustomMethodSecurityExpressionRoot(Authentication authentication) {
        super(authentication);
    }

    private Object filterObject;
    private Object returnObject;

    public boolean hasPrivilege(String permission){
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        return authorities.stream()
                    .map(GrantedAuthority::getAuthority)
                    .filter(item -> !item.startsWith(CloudConstant.ROLE_PREFIX))
                    .anyMatch(x -> antPathMatcher.match(x, permission));
    }
    ...
}
Copy the code
  • Custom methods intercept handlers
/ * * *@author http://www.javadaily.cn
 */
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {

    private AuthenticationTrustResolver trustResolver =  new AuthenticationTrustResolverImpl();

    @Override
    protected MethodSecurityExpressionOperations createSecurityExpressionRoot( Authentication authentication, MethodInvocation invocation) {
        CustomMethodSecurityExpressionRoot root =
                new CustomMethodSecurityExpressionRoot(authentication);
        root.setPermissionEvaluator(getPermissionEvaluator());
        root.setTrustResolver(this.trustResolver);
        root.setRoleHierarchy(getRoleHierarchy());
        returnroot; }}Copy the code
  • Enabling method verification
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler(a) {
        CustomMethodSecurityExpressionHandler expressionHandler =
                new CustomMethodSecurityExpressionHandler();
        returnexpressionHandler; }}Copy the code
  • Annotate methods that require permission validation
@ ApiOperation (" select interface ")
@GetMapping("/account/getByCode/{accountCode}")
@PreAuthorize("hasPrivilege('queryAccount')")
public ResultData<AccountDTO> getByCode(@PathVariable(value = "accountCode") String accountCode){
	log.info("get account detail,accountCode is :{}",accountCode);
	AccountDTO accountDTO = accountService.selectByCode(accountCode);
	return ResultData.success(accountDTO);
}
Copy the code
  • testThe debug command shows that the obtained user permissions are resource ids in the resource table.

summary

In my opinion, the most complex module in SpringCloud micro-service architecture is the authentication and authorization module of users. This paper solves the authorization problem and what you can do through two implementation methods. You can choose the specific implementation mode according to the actual business scenario. Of course, I still suggest using the first authorization method based on the path matcher, which only needs to intercept at the gateway layer.

SpringCloud Alibab is the 21st article in the series. If you are interested in the previous article, you can go to javadaily.cn/tags/Spring… Look at it.


Here for everyone to prepare a small gift, pay attention to the public number, enter the following code, you can get baidu network disk address, no routine to receive!

004: “Internet Architecture Teaching Video” 006: “SpringBoot Implementation of Ordering System” 007: “SpringSecurity combat video” 008: “Hadoop combat teaching video” 009: “Tencent 2019Techo Developer Conference PPT” 010: wechat communication group