Skip to content

Commit 86639a4

Browse files
committed
feat(security): 添加自定义动态权限 PermissionAuthorizationManager
1 parent ddaeb93 commit 86639a4

8 files changed

Lines changed: 199 additions & 21 deletions

File tree

security-spring-boot-starter/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- 多渠道登录
66
- 一个注解/一个配置,解决匿名url访问(忽略认证)
7+
- 基于数据库的动态权限
78

89
## USAGES
910

security-spring-boot-starter/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<groupId>com.admin4j.framework</groupId>
1111
<artifactId>security-spring-boot-starter</artifactId>
1212
<packaging>jar</packaging>
13-
<version>0.9.4-SNAPSHOT</version>
13+
<version>0.9.5-SNAPSHOT</version>
1414

1515
<name>security-spring-boot-starter</name>
1616

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.admin4j.framework.security.authorization;
2+
3+
import java.util.List;
4+
5+
/**
6+
* 权限uri 服务
7+
*
8+
* @author andanyang
9+
* @since 2023/12/19 14:34
10+
*/
11+
public interface IPermissionUriService {
12+
13+
/**
14+
* 获取 系统 所有的 PermissionUri
15+
*
16+
* @return
17+
*/
18+
List<String> allPermissionUri();
19+
20+
/**
21+
* 当前用户拥有的权限
22+
*
23+
* @return
24+
*/
25+
List<String> getMyPermissionUrls();
26+
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.admin4j.framework.security.authorization;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.security.authorization.AuthorizationDecision;
5+
import org.springframework.security.authorization.AuthorizationManager;
6+
import org.springframework.security.core.Authentication;
7+
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
8+
import org.springframework.util.AntPathMatcher;
9+
10+
import java.util.Collection;
11+
import java.util.function.Supplier;
12+
13+
/**
14+
* 自定义数据权限(授权)处理
15+
* 被授 AuthorizationFilter 调用,负责做出最终的访问控制决定
16+
*
17+
* @author andanyang
18+
* @since 2023/12/19 9:53
19+
*/
20+
@RequiredArgsConstructor
21+
public class PermissionAuthorizationManager implements AuthorizationManager<RequestAuthorizationContext> {
22+
23+
/**
24+
* 有权限
25+
*/
26+
protected static final AuthorizationDecision GRANTED = new AuthorizationDecision(true);
27+
/**
28+
* 没有权限
29+
*/
30+
protected static final AuthorizationDecision UN_AUTHORIZED = new AuthorizationDecision(false);
31+
32+
protected final IPermissionUriService permissionUriService;
33+
34+
protected AntPathMatcher antPathMatcher = new AntPathMatcher();
35+
36+
/**
37+
* Determines if access is granted for a specific authentication and object.
38+
*
39+
* @param authentication the {@link Supplier} of the {@link Authentication} to check
40+
* @param object the {@link T} object to check
41+
* @return an {@link AuthorizationDecision} or null if no decision could be made
42+
*/
43+
@Override
44+
public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext object) {
45+
46+
// 获取当前请求的 URL 地址
47+
String requestURI = object.getRequest().getRequestURI();
48+
boolean matchPermission = matchPermission(requestURI);
49+
if (matchPermission) {
50+
return GRANTED;
51+
}
52+
53+
// 沒有匹配到, 查看当前 requestURI 是否需要权限控制
54+
return urlNeedPermission(requestURI) ? UN_AUTHORIZED : GRANTED;
55+
}
56+
57+
/**
58+
* url 是否需要授权
59+
* TODO 放在 service 立马
60+
*
61+
* @return
62+
*/
63+
public boolean urlNeedPermission(String requestURI) {
64+
65+
Collection<String> allPermissionUrls = getAllPermissionUrls();
66+
for (String url : allPermissionUrls) {
67+
if (antPathMatcher.match(url, requestURI)) {
68+
return true;
69+
}
70+
}
71+
return false;
72+
}
73+
74+
/**
75+
* 当前用户是否可以匹配到访问该url权限
76+
*
77+
* @param requestURI
78+
* @return
79+
*/
80+
public boolean matchPermission(String requestURI) {
81+
Collection<String> permissionUrls = getPermissionUrls();
82+
83+
if (permissionUrls == null || permissionUrls.isEmpty()) {
84+
return false;
85+
}
86+
87+
for (String url : permissionUrls) {
88+
if (antPathMatcher.match(url, requestURI)) {
89+
return true;
90+
}
91+
}
92+
return false;
93+
}
94+
95+
/**
96+
* 当前用户拥有的权限
97+
*
98+
* @return
99+
*/
100+
public Collection<String> getPermissionUrls() {
101+
102+
return permissionUriService.getMyPermissionUrls();
103+
}
104+
105+
106+
/**
107+
* 获取全部权限
108+
*
109+
* @return
110+
*/
111+
protected Collection<String> getAllPermissionUrls() {
112+
return permissionUriService.allPermissionUri();
113+
}
114+
}

security-spring-boot-starter/src/main/java/com/admin4j/framework/security/configuration/MultiAuthenticationManagerAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public MultiUserDetailsService usernamePasswordUserDetailsService(
5050

5151

5252
/**
53-
* 获取 AuthenticationManager
53+
* 获取 PermissionAuthorizationManager
5454
* <p>
5555
* 或者:
5656
* <code>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.admin4j.framework.security.configuration;
2+
3+
import com.admin4j.framework.security.authorization.IPermissionUriService;
4+
import com.admin4j.framework.security.authorization.PermissionAuthorizationManager;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
6+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7+
import org.springframework.context.annotation.Bean;
8+
9+
/**
10+
* @author andanyang
11+
* @since 2023/12/19 14:40
12+
*/
13+
@ConditionalOnBean(IPermissionUriService.class)
14+
@ConditionalOnMissingBean(PermissionAuthorizationManager.class)
15+
public class PermissionAutoConfiguration {
16+
17+
@Bean
18+
public PermissionAuthorizationManager permissionAuthorizationManager(IPermissionUriService permissionUriService) {
19+
20+
return new PermissionAuthorizationManager(permissionUriService);
21+
}
22+
}

security-spring-boot-starter/src/main/java/com/admin4j/framework/security/configuration/SecurityConfiguration.java

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.admin4j.framework.security.configuration;
22

33
import com.admin4j.framework.security.ISecurityIgnoringUrl;
4+
import com.admin4j.framework.security.authorization.PermissionAuthorizationManager;
45
import com.admin4j.framework.security.filter.ActuatorFilter;
56
import com.admin4j.framework.security.ignoringUrl.AnonymousAccessUrl;
67
import com.admin4j.framework.security.multi.MultiSecurityConfigurerAdapter;
@@ -16,8 +17,9 @@
1617
import org.springframework.context.annotation.Bean;
1718
import org.springframework.http.HttpMethod;
1819
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
20+
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
1921
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
20-
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
22+
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
2123
import org.springframework.security.config.http.SessionCreationPolicy;
2224
import org.springframework.security.web.AuthenticationEntryPoint;
2325
import org.springframework.security.web.SecurityFilterChain;
@@ -33,7 +35,7 @@
3335

3436
/**
3537
* TODO 需要注入,取消 UserDetailsServiceAutoConfiguration 开启
36-
* value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
38+
* value = { PermissionAuthorizationManager.class, AuthenticationProvider.class, UserDetailsService.class,
3739
* AuthenticationManagerResolver.class },
3840
*
3941
* @author andanyang
@@ -79,7 +81,8 @@ public class SecurityConfiguration {
7981
CorsFilter corsFilter;
8082
@Autowired(required = false)
8183
MultiSecurityConfigurerAdapter multiSecurityConfigurerAdapter;
82-
84+
@Autowired(required = false)
85+
PermissionAuthorizationManager permissionAuthorizationManager;
8386
/**
8487
* 取消ROLE_前缀
8588
*/
@@ -131,11 +134,6 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti
131134
// 添加Logout filter
132135
httpSecurity.logout().logoutUrl(formLoginProperties.getLogOutProcessingUrl()).permitAll().logoutSuccessHandler(logoutSuccessHandler);
133136

134-
// 授权请求配置
135-
// 忽略URl配置
136-
ignoringRequestMatcherRegistry(httpSecurity.authorizeRequests());
137-
// 除上面外的所有请求全部需要鉴权认证;其他路径必须验证
138-
httpSecurity.authorizeRequests().anyRequest().authenticated();
139137

140138
// 添加CORS filter
141139
if (corsFilter != null) {
@@ -160,14 +158,31 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti
160158
.permitAll();
161159
}
162160

161+
// 授权请求配置 authorizeHttpRequests(6.0 新版) authorizeRequests(旧版) 区别
162+
// httpSecurity.authorizeRequests().anyRequest().authenticated();
163+
httpSecurity.authorizeHttpRequests(register -> {
164+
165+
// 忽略URl配置
166+
ignoringRequestMatcherRegistry(register);
167+
if (permissionAuthorizationManager != null) {
168+
// 自定义授权
169+
register.anyRequest().access(permissionAuthorizationManager);
170+
} else {
171+
// 除上面外的所有请求全部需要鉴权认证;其他路径必须验证
172+
register.anyRequest().authenticated();
173+
}
174+
175+
});
176+
163177
return httpSecurity.build();
164178
}
165179

166180

167181
/**
168182
* 忽略URl配置
169183
*/
170-
private void ignoringRequestMatcherRegistry(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry) {
184+
private void ignoringRequestMatcherRegistry(AbstractRequestMatcherRegistry<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizedUrl> matcherRegistry) {
185+
171186

172187
if (securityIgnoringUrls != null && !securityIgnoringUrls.isEmpty()) {
173188
securityIgnoringUrls.forEach(url -> {
@@ -177,33 +192,33 @@ private void ignoringRequestMatcherRegistry(ExpressionUrlAuthorizationConfigurer
177192
}
178193

179194
if (url.support() == null) {
180-
expressionInterceptUrlRegistry.antMatchers(url.ignoringUrls()).permitAll();
195+
matcherRegistry.mvcMatchers(url.ignoringUrls()).permitAll();
181196
} else {
182-
expressionInterceptUrlRegistry.antMatchers(url.support(), url.ignoringUrls()).permitAll();
197+
matcherRegistry.antMatchers(url.support(), url.ignoringUrls()).permitAll();
183198
}
184199
});
185200
}
186201

187202
if (ignoringUrlProperties != null) {
188203

189204
if (ignoringUrlProperties.getUris() != null && ignoringUrlProperties.getUris().length > 0) {
190-
expressionInterceptUrlRegistry.antMatchers(ignoringUrlProperties.getUris()).permitAll();
205+
matcherRegistry.antMatchers(ignoringUrlProperties.getUris()).permitAll();
191206
}
192207
if (ignoringUrlProperties.getGet() != null && ignoringUrlProperties.getGet().length > 0) {
193-
expressionInterceptUrlRegistry.antMatchers(HttpMethod.GET, ignoringUrlProperties.getGet()).permitAll();
208+
matcherRegistry.antMatchers(HttpMethod.GET, ignoringUrlProperties.getGet()).permitAll();
194209
}
195210

196211
if (ignoringUrlProperties.getPost() != null && ignoringUrlProperties.getPost().length > 0) {
197-
expressionInterceptUrlRegistry.antMatchers(HttpMethod.POST, ignoringUrlProperties.getPost()).permitAll();
212+
matcherRegistry.antMatchers(HttpMethod.POST, ignoringUrlProperties.getPost()).permitAll();
198213
}
199214
if (ignoringUrlProperties.getPut() != null && ignoringUrlProperties.getPut().length > 0) {
200-
expressionInterceptUrlRegistry.antMatchers(HttpMethod.PUT, ignoringUrlProperties.getPut()).permitAll();
215+
matcherRegistry.antMatchers(HttpMethod.PUT, ignoringUrlProperties.getPut()).permitAll();
201216
}
202217
if (ignoringUrlProperties.getPatch() != null && ignoringUrlProperties.getPatch().length > 0) {
203-
expressionInterceptUrlRegistry.antMatchers(HttpMethod.PATCH, ignoringUrlProperties.getPatch()).permitAll();
218+
matcherRegistry.antMatchers(HttpMethod.PATCH, ignoringUrlProperties.getPatch()).permitAll();
204219
}
205220
if (ignoringUrlProperties.getDelete() != null && ignoringUrlProperties.getDelete().length > 0) {
206-
expressionInterceptUrlRegistry.antMatchers(HttpMethod.DELETE, ignoringUrlProperties.getDelete()).permitAll();
221+
matcherRegistry.antMatchers(HttpMethod.DELETE, ignoringUrlProperties.getDelete()).permitAll();
207222
}
208223
}
209224

@@ -213,7 +228,7 @@ private void ignoringRequestMatcherRegistry(ExpressionUrlAuthorizationConfigurer
213228
Map<HttpMethod, String[]> anonymousUrl = anonymousAccessUrl.getAnonymousUrl();
214229
anonymousUrl.keySet().forEach(i -> {
215230

216-
expressionInterceptUrlRegistry.antMatchers(i, anonymousUrl.get(i)).permitAll();
231+
matcherRegistry.antMatchers(i, anonymousUrl.get(i)).permitAll();
217232
});
218233
}
219234
}

security-spring-boot-starter/src/main/java/com/admin4j/framework/security/multi/MultiAuthenticationFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public Authentication attemptAuthentication(HttpServletRequest request, HttpServ
4747

4848
MultiAuthenticationToken token = obtainToken(request);
4949
setDetails(request, token);
50-
// 匹配成功交给 AuthenticationManager 去认证
50+
// 匹配成功交给 PermissionAuthorizationManager 去认证
5151
return this.getAuthenticationManager().authenticate(token);
5252
}
5353

0 commit comments

Comments
 (0)