Security授权实现
授权
重点剖析
AccessDecisionManager
AccessDecisionManager (访问决策管理器),用来决定此次访问是否被允许。
AccessDecisionVoter
AccessDecisionVoter (访问决定投票器),投票器会检查用户是否具备应有的角色,进而投出赞成、反对或者弃权票。
AccesDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在 AccessDecisionManager 中会换个遍历 AccessDecisionVoter,进而决定是否允许用户访问,因而 AaccesDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider 和 ProviderManager 的关系。
ConfigAttribute
ConfigAttribute,用来保存授权时的角色信息
在 Spring Security 中,用户请求一个资源(通常是一个接口或者一个 Java 方法)需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute 中只有一个 getAttribute方法,该方法返回一个 String 字符串,就是角色的名称。一般来说,角色名称都带有一个 ROLE_
前缀,投票器 AccessDecisionVoter 所做的事情,其实就是比较用户所具各的角色和请求某个
资源所需的 ConfigAtuibute 之间的关系。
调用步骤
首先. 调用对应接口时,需要的角色会被封装到ConfigAttribute
对象中来保存权限信息 ,只有保存了权限信息才能进行调用 ,在ConfigAttribute
中有方法名为getAttribute
的,它返回的是一个字符串,就是角色名称。
接下来. 通过AccessDecisionManager
(访问决策管理器) 来对不同的权限设置不同的AccessDecisionVoter
(访问决定投票器),从而实现决定用户是否能够访问
所需的拦截器FilterSecurityInterceptor
实现案例
实战
在前面的案例中,我们配置的 URL 拦截规则和请求 URL 所需要的权限都是通过代码来配置的,这样就比较死板,如果想要调整访问某一个 URL 所需要的权限,就需要修改代码。
动态管理权限规则就是我们将 URL 拦截规则和访问 URI 所需要的权限都保存在数据库中,这样,在不修改源代码的情况下,只需要修改数据库中的数据,就可以对权限进行调整。
用户<--中间表--> 角色 <--中间表--> 菜单
库表设计
1 | SET NAMES utf8mb4; |
创建 springboot 应用
引入依赖
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<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!--数据库相关的-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>配置配置文件
1
2
3
4
5
6
7
8server.port=8080
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/security?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:com/blr/mapper/*.xml
mybatis.type-aliases-package=com.blr.entity创建实体类
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
75public class User implements UserDetails {
private Integer id;
private String password;
private String username;
private boolean enabled;
private boolean locked;
private List<Role> roles;
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(r -> new SimpleGrantedAuthority(r.getName())).collect(Collectors.toList());
}
public String getPassword() {
return password;
}
public String getUsername() {
return username;
}
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return !locked;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return enabled;
}
public void setId(Integer id) {
this.id = id;
}
public void setPassword(String password) {
this.password = password;
}
public void setUsername(String username) {
this.username = username;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Integer getId() {
return id;
}
public List<Role> getRoles() {
return roles;
}
}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
29public class Role {
private Integer id;
private String name;
private String nameZh;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNameZh() {
return nameZh;
}
public void setNameZh(String nameZh) {
this.nameZh = nameZh;
}
}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
29public class Menu {
private Integer id;
private String pattern;
private List<Role> roles;
public List<Role> getRoles() {
return roles;
}
public void setRoles(List<Role> roles) {
this.roles = roles;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPattern() {
return pattern;
}
public void setPattern(String pattern) {
this.pattern = pattern;
}
}创建 mapper 接口
1
2
3
4
5
public interface UserMapper {
List<Role> getUserRoleByUid(Integer uid);
User loadUserByUsername(String username);
}1
2
3
4
public interface MenuMapper {
List<Menu> getAllMenu();
}创建 mapper 文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<mapper namespace="com.blr.mapper.UserMapper">
<select id="loadUserByUsername" resultType="com.blr.entity.User">
select *
from user
where username = #{username};
</select>
<select id="getUserRoleByUid" resultType="com.blr.entity.Role">
select r.*
from role r,
user_role ur
where ur.uid = #{uid}
and ur.rid = r.id
</select>
</mapper>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<mapper namespace="com.blr.mapper.MenuMapper">
<resultMap id="MenuResultMap" type="com.blr.entity.Menu">
<id property="id" column="id"/>
<result property="pattern" column="pattern"></result>
<collection property="roles" ofType="com.blr.entity.Role">
<id column="rid" property="id"/>
<result column="rname" property="name"/>
<result column="rnameZh" property="nameZh"/>
</collection>
</resultMap>
<select id="getAllMenu" resultMap="MenuResultMap">
select m.*, r.id as rid, r.name as rname, r.nameZh as rnameZh
from menu m
left join menu_role mr on m.`id` = mr.`mid`
left join role r on r.`id` = mr.`rid`
</select>
</mapper>创建 service 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserService implements UserDetailsService {
private final UserMapper userMapper;
public UserService(UserMapper userMapper) {
this.userMapper = userMapper;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.loadUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
user.setRoles(userMapper.getUserRoleByUid(user.getId()));
return user;
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
public class MenuService {
private final MenuMapper menuMapper;
public MenuService(MenuMapper menuMapper) {
this.menuMapper = menuMapper;
}
public List<Menu> getAllMenu() {
return menuMapper.getAllMenu();
}
}创建测试 controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HelloController {
public String admin() {
return "hello admin";
}
public String user() {
return "hello user";
}
public String guest() {
return "hello guest";
}
public String hello() {
return "hello";
}
}创建 CustomSecurityMetadataSource
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
public class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
private final MenuService menuService;
public CustomSecurityMetadataSource(MenuService menuService) {
this.menuService = menuService;
}
AntPathMatcher antPathMatcher = new AntPathMatcher();
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
List<Menu> allMenu = menuService.getAllMenu();
for (Menu menu : allMenu) {
if (antPathMatcher.match(menu.getPattern(), requestURI)) {
String[] roles = menu.getRoles().stream().map(r -> r.getName()).toArray(String[]::new);
return SecurityConfig.createList(roles);
}
}
return null;
}
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}配置 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
52
53
54
55
56
57package Authorize.config;
import Authorize.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 将之前定义的权限数据源 注入
*/
private final CustomerSecurityMetaSource customerSecurityMetaSource;
private final UserService userService;
public SecurityConfig(CustomerSecurityMetaSource customerSecurityMetaSource, UserService userService) {
this.customerSecurityMetaSource = customerSecurityMetaSource;
this.userService = userService;
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService);
}
protected void configure(HttpSecurity http) throws Exception {
//1. 获取工厂对象
ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);
//2. 设置自定义的url权限处理
http.apply(new UrlAuthorizationConfigurer<>(applicationContext))
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setSecurityMetadataSource(customerSecurityMetaSource);
object.setRejectPublicInvocations(false);
return object;
}
});
http.formLogin()
.and()
.csrf().disable();
}
}启动入口类进行测试
说明
部分的代码是由@编程不良人
的学习教程中提供的,仅作为自己的学习参考使用!
作者教程链接 : Spring Security 最新实战教程