Spring Security 访问控制

本文将介绍 Spring Security 的访问控制功能的流程、配置及代码。

一、配置示例

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
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

···

// 配置访问控制
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 开启通过URL匹配从而进行访问控制的模式
.authorizeRequests()
// 首页、登录页、错误页,不需要权限即可访问
.antMatchers("/", "/index", "/login", "/error").permitAll()
// "/admin" 及其以下所有路径,都需要 "ADMIN" 权限
.antMatchers("/admin/**").hasRole("ADMIN")
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
// 自定义资源及其权限的获取
object.setSecurityMetadataSource(mySecurityMetadataSource);
// 自定义资源的访问控制
object.setAccessDecisionManager(myAccessDecisionManager);
return object;
}
})
.and()
// 配置登录页、登录后跳转页、身份验证方式、登录参数名称
.formLogin()
.loginPage("/login_p")
.loginProcessingUrl("/login")
.permitAll()
.usernameParameter(自定义用户名参数)
.passwordParameter(自定义密码参数)
.and()
// 配置登出URL、登出后跳转页、身份验证方式
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()
.and()
// 开启异常处理功能,并指定"拒绝访问异常"的处理类
.exceptionHandling().accessDeniedHandler(异常处理类)
.and()
// 禁用跨站请求保护
.csrf().disable()
.and()
// 开启跨域功能
.cors();
}

}

二、配置 - 资源权限信息

1. 开启 URL 匹配模式

1
2
3
4
5
6
7
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 开启通过URL匹配从而进行访问控制的模式
.authorizeRequests()
···
}

2. 在配置类中指定

1
2
3
4
5
6
7
8
9
10
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 开启通过URL匹配从而进行访问控制的模式
.authorizeRequests()
// 首页、登录页、错误页,不需要权限即可访问
.antMatchers("/", "/index", "/login", "/error").permitAll()
// "/admin" 及其以下所有路径,都需要 "ADMIN" 权限
.antMatchers("/admin/**").hasRole("ADMIN")
}

3. 自定义资源权限信息获取

  • 定义资源权限信息获取类

    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
    @Component
    public class MySecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    ResourceDao resourceDao;

    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
    // 获取要访问的资源 URL
    String requestUrl = ((FilterInvocation) object).getRequestUrl();
    // 获取所有 Resource,遍历进行路径匹配,匹配成功则返回资源允许访问的角色列表
    for (Resource resource : resourceDao.findAll()) {
    if (antPathMatcher.match(resource.getUrl(), requestUrl) && resource.getRolesArray().length > 0) {
    return SecurityConfig.createList(resource.getRolesArray());
    }
    }
    //匹配不成功,则返回 ROLE_NONE,表示不需要角色即可访问
    return SecurityConfig.createList("ROLE_NONE");
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
    return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
    return FilterInvocation.class.isAssignableFrom(clazz);
    }
    }
  • 配置资源权限信息获取类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http
    // 开启通过URL匹配从而进行访问控制的模式
    .authorizeRequests()
    // 首页、登录页、错误页,不需要权限即可访问
    .antMatchers("/", "/index", "/login", "/error").permitAll()
    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    @Override
    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
    // 自定义资源及其权限的获取
    object.setSecurityMetadataSource(mySecurityMetadataSource);
    return object;
    }
    })
    }

4. FilterInvocationSecurityMetadataSource

FilterInvocationSecurityMetadataSource 接口继承自 SecurityMetadataSource 接口。

5. SecurityMetadataSource

主要作用是:根据对象,获取对象所需的权限信息,将权限信息以集合的方式返回。

它的代码大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface SecurityMetadataSource extends AopInfrastructureBean {

/**
* 获取对象所需的权限信息
*/
Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException;

/**
* 获取所有对象的权限信息
* 该方法主要目的是被 AbstractSecurityInterceptor 用于在启动时校验每个权限对象
*/
Collection<ConfigAttribute> getAllConfigAttributes();

/**
* 表名支持的安全对象的类型
*/
boolean supports(Class<?> clazz);

}

三、配置 - 访问控制管理器

1. 配置方式

  • 定义访问控制器类

    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
    @Component
    public class MyAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
    throws AccessDeniedException, InsufficientAuthenticationException {
    // 获取当前用户具有的角色
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

    // configAttributes 由 SecurityMetadataSource 的 getAttributes() 获取,是资源允许访问的角色列表
    for (ConfigAttribute attribute : configAttributes) {
    String role = attribute.getAttribute();
    // 资源不需要角色,直接 return
    if ("ROLE_NONE".equals(role)) {
    return;
    }
    for (GrantedAuthority authority : authorities) {
    // 匹配成功,直接 return
    if (authority.getAuthority().equals(role)) {
    return;
    }
    }
    }
    // 匹配失败
    throw new AccessDeniedException("你没有访问" + object + "的权限!");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
    return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
    return true;
    }
    }
  • 配置访问控制器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http
    // 开启通过URL匹配从而进行访问控制的模式
    .authorizeRequests()
    // 首页、登录页、错误页,不需要权限即可访问
    .antMatchers("/", "/index", "/login", "/error").permitAll()
    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
    @Override
    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
    // 自定义资源的访问控制管理器
    object.setAccessDecisionManager(myAccessDecisionManager);
    return object;
    }
    })
    }

2. AccessDecisionManager

主要作用是判断用户能否访问指定的资源。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface AccessDecisionManager {

/**
* 判断能否访问资源
* 如果能,则什么也不做;
* 如果不能,则抛出异常
* @param authentication 资源的调用者
* @param object 被访问的资源对象
* @param configAttributes 资源所需要的权限列表
*/
void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException;

/**
* Indicates whether t
*/
boolean supports(ConfigAttribute attribute);

/**
* Indicates whethe
*/
boolean supports(Class<?> clazz);

}

四、配置 - 登录

1
2
3
4
5
6
7
http
.formLogin()
.loginPage("/login_p")
.loginProcessingUrl("/login")
.permitAll()
.usernameParameter(自定义用户名参数)
.passwordParameter(自定义密码参数)

五、配置 - 登出

1
2
3
4
5
http
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.permitAll()

六、配置 - 异常处理

  • 定义异常处理类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Component
    public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.setContentType("application/json;charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.write("拒绝访问:" + accessDeniedException.getMessage());
    out.flush();
    out.close();
    }
    }
  • 配置异常处理类

    1
    2
    http
    .exceptionHandling().accessDeniedHandler(异常处理类)

参考