• SpringSecurity Oauth2实战 - 10 自定义SpEL权限表达式配置白名单url不需要token认证和鉴权


    1. 同时配置url和权限表达式

    相关文章:

    SpringSecurity Oauth2实战 - 08 SpEL权限表达式源码分析及两种权限控制方式原理

    在前面文章中,我们分析了权限表达式的实现原理,并通过 debug 看了 url 权限表达式和注解权限表达式的调用过程,最终看到 url 权限表达式会在注解权限表达式之前执行,那么如果我们在资源服务器配置类 ResourceServerAutoConfiguration中配置了 permitAll 权限表达式,在方法注解中配置了 hasAuthority 权限表达式会怎么样呢?

    1. /api/v1/doc 配置 antMatchers(“/api/v1/doc”).permitAll()

    @Slf4j
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private TokenStore tokenStore;
    
        @Value("${spring.application.name}")
        private String appName;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(appName);
            resources.tokenStore(tokenStore);
            resources.tokenExtractor(tokenExtractor());
        }
    
        @Bean
        @Primary
        public TokenExtractor tokenExtractor() {
            CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
            return customTokenExtractor;
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/api/v1/login", "/api/v1/token").permitAll()
                	 // 配置/api/v1/doc请求路径不需要认证就可以访问
                    .antMatchers("/api/v1/doc").permitAll();
            http.authorizeRequests().anyRequest().authenticated();
            http.formLogin().disable();
            http.httpBasic().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    2. /api/v1/doc 配置 @PreAuthorize(“hasAuthority(‘knowledge’)”)

    @RestController
    @RequestMapping("/api/v1")
    public class DocController {
    
        @PreAuthorize("hasAuthority('roleEdit')")
        @GetMapping("/doc")
        public String getDocList(){
            return "doc";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3. 启动项目测试

    用户具有的权限有:userEdit、superAdmin、knowledgeQuery、userQuery、knowledgeEdit

    antMatchers("/api/v1/doc").permitAll():指定用户不需要授权就可以访问/api/v1/doc

    @PreAuthorize("hasAnyAuthority('roleEdit')"):指定用户具备roleEdit权限才能访问 /api/v1/doc

    因为 url 权限表达式会在注解权限表达式之前执行,因此将以注解权限表达式为准,用户没有权限访问;

    在这里插入图片描述

    ① 请求进入过滤器 OAuth2AuthenticationProcessingFilter 获取用户的认证信息

    在这里插入图片描述

    ② 因为该请求路径api/v1/doc没有在拦截器配置类中放行,因此请求在进入Controller层方法之前会被拦截器拦截,在拦截器中判断用户是否已经认证,如果用户没有认证将不会放行。

    @Configuration
    @EnableWebMvc
    public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {
    
        @Bean
        public UserInfoInterceptor userInfoInterceptor() {
            return new UserInfoInterceptor();
        }
    
        @Override
        public void addInterceptors(@NonNull InterceptorRegistry registry) {
            registry.addInterceptor(userInfoInterceptor())
                    // 放行的请求
                    .excludePathPatterns("/api/v1/login");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    ③ 请求进入SecurityExpressionRoot类的 hasAuthority 方法而不是permitAll方法,说明最终请求会以注解表达式中配置的为准。

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

    2. 配置白名单url不需要token认证

    如果想要白名单不需要token认证:

    1. 在 ResourceServerAutoConfiguration 中配置白名单url的权限表达式为 permitAll,代表不需要accessToken认证也能访问,不做任何鉴权。
    2. 在 CommonWebMvcAutoConfiguration 中配置白名单url放行;

    1. 白名单属性配置类 WhiteUrlAutoConfiguration

    @Slf4j
    @Data
    @Configuration
    @ConfigurationProperties(prefix = "chahua")
    @EnableConfigurationProperties
    public class WhiteUrlAutoConfiguration implements InitializingBean {
    
        /**
         * 白名单url:不需要token认证
         */
        private Set<String> whiteUrls = new HashSet<>();
    
        @Override
        public void afterPropertiesSet() {
            if (whiteUrls != null && whiteUrls.size() > 0) {
                log.info("Load {} succeed: {}", "white-urls.yml", String.join(", ", whiteUrls));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2. 资源服务器配置类 ResourceServerAutoConfiguration

    @Slf4j
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
    
        @Autowired
        private WhiteUrlAutoConfiguration whiteUrlAutoConfiguration;
    
        @Autowired
        private TokenStore tokenStore;
    
        @Value("${spring.application.name}")
        private String appName;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(appName);
            resources.tokenStore(tokenStore);
            resources.tokenExtractor(tokenExtractor());
        }
    
        @Bean
        @Primary
        public TokenExtractor tokenExtractor() {
            CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
            return customTokenExtractor;
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/api/v1/login", "/api/v1/token").permitAll();
            // 配置白名单url不需要token认证和授权就可以访问
            Set<String> whiteUrls = whiteUrlAutoConfiguration.getWhiteUrls();
            if (whiteUrls.size() > 0) {
                String[] urlPatterns = whiteUrls.toArray(new String[0]);
                http.authorizeRequests(authorize -> authorize.antMatchers(urlPatterns).permitAll());
            }
            http.authorizeRequests().anyRequest().authenticated();
            http.formLogin().disable();
            http.httpBasic().disable();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    3. 配置拦截器 CommonWebMvcAutoConfiguration

    public class UserInfoInterceptor extends HandlerInterceptorAdapter {
        /**
         * 拦截所有请求,在Controller层方法之前调用
         */
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 判断用户是否被认证,如果没有认证不放行
            boolean isAuthenticated = request.authenticate(response);
            if (!isAuthenticated) {
                return false;
            }
            // 存储用户信息到本地线程
            Principal userPrincipal = request.getUserPrincipal();
            OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) userPrincipal;
            AuthUser ngsocUser = (AuthUser) oAuth2Authentication.getUserAuthentication().getPrincipal();
            UserInfo userInfo = ngsocUser.getUserInfo();
            UserInfoShareHolder.setUserInfo(userInfo);
            // 放行,继续执行Controller层的方法
            return true;
        }
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            UserInfoShareHolder.remove();
            super.afterCompletion(request, response, handler, ex);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    因为我们在项目中引入了拦截器,该拦截器会在Controller层方法执行之前拦截所有请求,判断用户是否认证,如果用户未认证请求将不会放行,因此需要配置白名单url放行;

    @Configuration
    @EnableWebMvc
    public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {
    
        @Value("${spring.application.name}")
        private String appName;
    
        @Autowired
        private WhiteUrlAutoConfiguration whiteUrlAutoConfiguration;
    
        @Bean
        public UserInfoInterceptor userInfoInterceptor() {
            return new UserInfoInterceptor();
        }
    
        @Override
        public void addInterceptors(@NonNull InterceptorRegistry registry) {
            // 拦截器会拦截所有请求,需要配置放行的请求
            Set<String> whiteUrls = whiteUrlAutoConfiguration.getWhiteUrls();
            if("authority".equals(appName)){
                whiteUrls.add("/api/v1/login");
                whiteUrls.add("/api/v1/token");
            }
            registry.addInterceptor(userInfoInterceptor())
                    // 因为白名单url不需要token认证就可以访问,如果不放行,拦截器的preHandle()方法会返回false
                    .excludePathPatterns(whiteUrls.toArray(new String[0]));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    4. knowledge 业务服务

    @RestController
    @RequestMapping("/api/v1")
    public class DocController {
    
        @GetMapping("/doc")
        public String getDocList(){
            return "doc";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    配置文件中配置白名单 url :

    # 配置白名单url
    chahua:
      white-urls:
        - /api/v1/doc
    
    • 1
    • 2
    • 3
    • 4

    5. 启动项目测试

    不携带 accessToken 访问/aoi/v1/doc

    在这里插入图片描述

    ① 请求进入过滤器 OAuth2AuthenticationProcessingFilter,可以看到 accessToken=null,authentication=null:

    在这里插入图片描述

    ② 请求进入 DocController 类的 getDocList 方法:

    因为 ResourceServerAutoConfiguration 类中配置了资源不需要认证就可以访问,且拦截器配置了资源放行,因此请求直接进入了DocController中。

    在这里插入图片描述

    在这里插入图片描述

    3. 配置白名单url不需要token认证也不需要鉴权

    我们知道除了 http.authorizeRequests(authorize -> authorize.antMatchers(urlPatterns).permitAll()) 不做校验,所有的请求都会走到@PreAuthorize注解对应的方法里面,所以如果我们的请求url方法上配置了@PreAuthorize注解,即使在ResourceServerAutoConfiguration 配置了permitAll,仍然会进行鉴权。

    在 @PreAuthorize 注解中常用的 hasAuthority、hasPermission、hasRole、hasAnyRole 都是由 SecurityExpressionRoot 类提供的,且他们都调用了 SecurityExpressionRoot 类的 hasAnyAuthorityName 方法完成鉴权,因此我们理论上只需要实现一个自定义权限表达式类继承 SecurityExpressionRoot 类并重写该类的hasAnyAuthorityName 方法即可。但是该方法是私有的,子类无法重写。因此我们可以直接自定义一个自定义权限表达式类但是不继承SecurityExpressionRoot 类,而是直接在该类中实现一个SecurityExpressionRoot 类的功能。

    1. 自定义权限表达式类 CustomMethodSecurityExpressionRoot

    public class CustomMethodSecurityExpressionRoot implements MethodSecurityExpressionOperations{
    
        @Setter
        private RequestMatcher requestMatcher;
    
        /**
         * MethodSecurityExpressionOperations 接口方法的属性
         */
        private Object filterObject;
        private Object returnObject;
        private Object target;
    
        /**
         *  SecurityExpressionRoot 类中的属性
         */
        protected Authentication authentication;
        private AuthenticationTrustResolver trustResolver;
        private RoleHierarchy roleHierarchy;
        private Set<String> roles;
        private String defaultRolePrefix = "ROLE_";
        private PermissionEvaluator permissionEvaluator;
    
        /**
         * 判断是否是白名单WhiteUrl,不校验权限,不需要token
         */
        private boolean isWhiteUrl() {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            if (requestMatcher != null && requestMatcher.matches(request)) {
                // 白名单url,直接返回true
                return true;
            }
            return false;
        }
    
        /**
         * 修改 SecurityExpressionRoot 类中的该方法
         *
         * 登录请求url是否是白名单WhiteUrl,如果是则不需要校验权限,直接返回true
         */
        private boolean hasAnyAuthorityName(String prefix, String... roles) {
            // 如果是白名单WhiteUrl或者公共CommonUrl,则不需要校验权限,直接返回true
            if (isWhiteUrl()) {
                return true;
            }
            Set<String> roleSet = getAuthoritySet();
            for (String role : roles) {
                String defaultedRole = getRoleWithDefaultPrefix(prefix, role);
                if (roleSet.contains(defaultedRole)) {
                    return true;
                }
            }
            return false;
        }
    
    
    
        /**
         * 下面的方法都是 SecurityExpressionRoot 类中的实现方法,没有更改
         */
    
        public void setAuthentication(Authentication authentication) {
            if (authentication == null) {
                throw new IllegalArgumentException("Authentication object cannot be null");
            }
            this.authentication = authentication;
        }
    
        public CustomMethodSecurityExpressionRoot(Authentication authentication) {
            if (authentication == null) {
                throw new IllegalArgumentException("Authentication object cannot be null");
            }
            this.authentication = authentication;
        }
    
        @Override
        public Authentication getAuthentication() {
            return authentication;
        }
    
        // 判断当前用户具备的权限信息,是否存在指定权限
        @Override
        public final boolean hasAuthority(String authority) {
            return hasAnyAuthority(authority);
        }
    
        // 判断当前用户具备的权限信息,是否存在指定权限中的任意一个
        @Override
        public final boolean hasAnyAuthority(String... authorities) {
            return hasAnyAuthorityName(null, authorities);
        }
    
        // 判断当前用户具备的权限信息,是否存在指定角色
        @Override
        public final boolean hasRole(String role) {
            return hasAnyRole(role);
        }
    
        // 判断当前用户具备的权限信息,是否存在指定角色中的任意一个
        @Override
        public final boolean hasAnyRole(String... roles) {
            return hasAnyAuthorityName(defaultRolePrefix, roles);
        }
    
        // 允许所有的请求调用
        @Override
        public final boolean permitAll() {
            return true;
        }
    
        // 拒绝所有的请求调用
        @Override
        public final boolean denyAll() {
            return false;
        }
    
        // 当前用户是否是一个匿名用户
        @Override
        public final boolean isAnonymous() {
            return trustResolver.isAnonymous(authentication);
        }
    
        // 判断用户是否已经认证成功
        @Override
        public final boolean isAuthenticated() {
            return !isAnonymous();
        }
    
        // 当前用户是否通过RememberMe自动登录
        @Override
        public final boolean isRememberMe() {
            return trustResolver.isRememberMe(authentication);
        }
    
        // 当前登录用户是否既不是匿名用户又不是通过RememberMe登录的
        @Override
        public final boolean isFullyAuthenticated() {
            return !trustResolver.isAnonymous(authentication) && !trustResolver.isRememberMe(authentication);
        }
    
        public Object getPrincipal() {
            return authentication.getPrincipal();
        }
    
        public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
            this.trustResolver = trustResolver;
        }
    
        public void setRoleHierarchy(RoleHierarchy roleHierarchy) {
            this.roleHierarchy = roleHierarchy;
        }
    
        public void setPermissionEvaluator(PermissionEvaluator permissionEvaluator) {
            this.permissionEvaluator = permissionEvaluator;
        }
    
        // 当前登录用户是否具有指定目标的指定权限
        @Override
        public boolean hasPermission(Object target, Object permission) {
            return permissionEvaluator.hasPermission(authentication, target, permission);
        }
    
        // 当前登录用户是否具有指定目标的指定权限
        @Override
        public boolean hasPermission(Object targetId, String targetType, Object permission) {
            return permissionEvaluator.hasPermission(authentication, (Serializable) targetId, targetType, permission);
        }
    
        private Set<String> getAuthoritySet() {
            if (roles == null) {
                roles = new HashSet<>();
                Collection<? extends GrantedAuthority> userAuthorities = authentication.getAuthorities();
                if (roleHierarchy != null) {
                    userAuthorities = roleHierarchy.getReachableGrantedAuthorities(userAuthorities);
                }
                roles = AuthorityUtils.authorityListToSet(userAuthorities);
            }
            return roles;
        }
    
        public void setDefaultRolePrefix(String defaultRolePrefix) {
            this.defaultRolePrefix = defaultRolePrefix;
        }
    
        private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) {
            if (role == null) {
                return role;
            }
            if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) {
                return role;
            }
            if (role.startsWith(defaultRolePrefix)) {
                return role;
            }
            return defaultRolePrefix + role;
        }
    
        /**
         * 下面的方法都是 MethodSecurityExpressionOperations 接口中的实现方法,没有更改
         */
    
        @Override
        public void setFilterObject(Object filterObject) {
            this.filterObject = filterObject;
        }
    
        @Override
        public Object getFilterObject() {
            return this.filterObject;
        }
    
        @Override
        public void setReturnObject(Object returnObject) {
            this.returnObject = returnObject;
        }
    
        @Override
        public Object getReturnObject() {
            return this.returnObject;
        }
    
        void setThis(Object target) {
            this.target = target;
        }
    
        @Override
        public Object getThis() {
            return target;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230

    2. 资源服务器配置类 ResourceServerAutoConfiguration

    @Slf4j
    @Configuration
    @EnableResourceServer
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class ResourceServerAutoConfiguration extends ResourceServerConfigurerAdapter {
        /**
         * 权限表达式的自定义处理
         */
        @Autowired
        private GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration;
    
        @Autowired
        private WhiteUrlAutoConfiguration whiteUrlAutoConfiguration;
    
        @Autowired
        private TokenStore tokenStore;
    
        @Value("${spring.application.name}")
        private String appName;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(appName);
            resources.tokenStore(tokenStore);
            resources.tokenExtractor(tokenExtractor());
        }
    
        @Bean
        @Primary
        public TokenExtractor tokenExtractor() {
            CustomTokenExtractor customTokenExtractor = new CustomTokenExtractor();
            return customTokenExtractor;
        }
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/api/v1/login", "/api/v1/token").permitAll();
            // 配置白名单url不需要token认证和授权就可以访问
            // 如果url接口上使用了@PreAuthorize注解权限表达式,那么这里就不需要配置permitAll了,即使配置了,逻辑也会被覆盖掉
            // 如果url接口上没有使用@PreAuthorize注解权限表达式,这里又没有配置permitAll,那么url接口访问就会没有权限
            // 为了防止url接口上没有使用@PreAuthorize注解权限表达式,这里仍然配置permitAll,让白名单url不需要token认证和鉴权也可以访问
            Set<String> whiteUrls = whiteUrlAutoConfiguration.getWhiteUrls();
            if (whiteUrls.size() > 0) {
                String[] urlPatterns = whiteUrls.toArray(new String[0]);
                http.authorizeRequests(authorize -> authorize.antMatchers(urlPatterns).permitAll());
            }
            http.authorizeRequests().anyRequest().authenticated();
            http.formLogin().disable();
            http.httpBasic().disable();
        }
    
        @Bean
        public GlobalMethodSecurityConfiguration globalMethodSecurityConfiguration() {
            List<MethodSecurityExpressionHandler> handlers = new ArrayList<>(1);
            handlers.add(customMethodSecurityExpressionHandler());
            globalMethodSecurityConfiguration.setMethodSecurityExpressionHandler(handlers);
            return globalMethodSecurityConfiguration;
        }
    
        @Bean
        public MethodSecurityExpressionHandler customMethodSecurityExpressionHandler() {
            CustomMethodSecurityExpressionHandler expressionHandler = new CustomMethodSecurityExpressionHandler();
            expressionHandler.setWhiteUrlAutoConfiguration(whiteUrlAutoConfiguration);
            return expressionHandler;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    3. 配置拦截器 CommonWebMvcAutoConfiguration

    @Configuration
    @EnableWebMvc
    public class CommonWebMvcAutoConfiguration implements WebMvcConfigurer {
    
        @Value("${spring.application.name}")
        private String appName;
    
        @Autowired
        private WhiteUrlAutoConfiguration whiteUrlAutoConfiguration;
    
        @Bean
        public UserInfoInterceptor userInfoInterceptor() {
            return new UserInfoInterceptor();
        }
    
        @Override
        public void addInterceptors(@NonNull InterceptorRegistry registry) {
            // 拦截器会拦截所有请求,需要配置放行的请求
            Set<String> whiteUrls = whiteUrlAutoConfiguration.getWhiteUrls();
            if("authority".equals(appName)){
                whiteUrls.add("/api/v1/login");
                whiteUrls.add("/api/v1/token");
            }
            registry.addInterceptor(userInfoInterceptor())
                    // 因为白名单url不需要token认证就可以访问,如果不放行,拦截器的preHandle()方法会返回false
                    .excludePathPatterns(whiteUrls.toArray(new String[0]));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    4. knowledge 业务服务置文件中配置白名单 url

    @RestController
    @RequestMapping("/api/v1")
    public class DocController {
    
    	// 用户不具备roleEdit权限,因此如果用户需要鉴权,那么就会返回无权限访问,如果不需要鉴权就会返回doc
        @PreAuthorize("hasAuthority('roleEdit')")
        @GetMapping("/doc")
        public String getDocList(){
            return "doc";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    # 配置白名单url
    chahua:
      white-urls:
        - /api/v1/doc
    
    • 1
    • 2
    • 3
    • 4

    5. 启动项目测试

    不携带 accessToken 访问/aoi/v1/doc

    在这里插入图片描述

    ① 因为没有携带token访问,因此过滤器 OAuth2AuthenticationProcessingFilter 中返回的 authentication=null,代表用户未认证:

    在这里插入图片描述

    ② 请求进入 @PreAuthorize(“hasAuthority(‘roleEdit’)”) 注解中配置的 hasAuthority 方法

    在这里插入图片描述

    ③ 请求进入重写的 hasAnyAuthorityName 方法,在该方法中会判断请求url是否在白名单url中,如果在就不需要鉴权,直接返回true:

    在这里插入图片描述

    在这里插入图片描述

    ③ 请求进入 DocController

    在这里插入图片描述

    在这里插入图片描述

    至此,我们就实现了白名单 url 不需要 token 认证也不需要鉴权就能访问。

  • 相关阅读:
    为保障小程序安全,安装SSL证书是必要的选择
    [Redis]-持久化方式
    嵌入式Linux 开发经验:编写用户态应用程序 ioctl 控制 misc 设备
    在线客服聊天系统源码_美观强大golang内核开发_二进制运行傻瓜式安装_附搭建教程...
    ant-design-vue 库 Loading 组件封装
    机器学习原理篇:基础数学理论 Ⅱ
    总结了30段极简的Python代码!(一)
    pipenv创建虚拟环境+创建django程序
    Docker容器设置自动启动的方法
    怎么选择好的游戏平台开发商?
  • 原文地址:https://blog.csdn.net/qq_42764468/article/details/127798459