0%

SpringSecurity

权限管理过程中的相关概念

主体(principal)

使用系统的用户或设备或从其它系统远程登录的用户等。简单说就是谁使用系统谁就是主体。

认证(authentication)

权限管理系统确认一个主体的身份,允许主体进入系统。简单说就是"主体"证明自己是谁。笼统地讲就是登录操作。

授权(authorization)

将操作系统的"权力"授予"主体",这样主体就具备了操作系统中特定功能的能力。

过程:

用户名、密码 -----> 认证 ----->加载角色、权限信息----->完整的主体----->权限验证----->资源

权限管理的主流框架

SpringSecurity

Spring技术栈的组成部分

特点:

  • 和Spring无缝整合
  • 全面的权限控制
  • 专门为Web开发而设计
    • 旧版本不能脱离Web环境使用
    • 新版本对整个框架进行了分层抽取,分成了核心模块和Web模块,单独引入 核心模块就可以脱离Web环境
  • 重量级

Shiro

Apache下的轻量级权限控制框架

特点:

  • 轻量级。Shiro主张的理念是将复杂的事情变简单,针对对性能有更高要求的互联网应用有更好表现。
  • 通用性
    • 好处:不局限于Web环境,可以脱离Web环境使用
    • 缺陷:在Web环境下一些特定的需求需要手动编写代码定制

使用SpringSecurity

1. 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring.security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring.security.version}</version>
</dependency>

2. 配置Filter

三大组件的加载顺序:listener->filter->servlet

所以需要将所有Spring配置文件都在SpringMVC中加载,即只使用DispatcherServlet创建的IOC容器,否则就会导致ContextLoaderListener创建Spring IOC容器,然后DelegatingFilterProxy在Spring IOC容器中找不到springSecurityFilterChain或找到的不能用(因为它需要作用于SpringMVC的各个Controller),最终就需要通过将所有Bean都放在SpringMVC加载时创建的IOC容器中来解决问题。

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

<!-- 配置SpringMVC前端控制器 -->
<servlet>
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 设置SpringMVC配置文件位置和名称 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</init-param>
<!-- 将DispatcherServlet的初始化时间提前到服务器启动时 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!-- &lt;!&ndash; 配置SpringMVC监听器,在服务器启动时加载Spring配置文件 &ndash;&gt;-->
<!-- <listener>-->
<!-- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>-->
<!-- </listener>-->

<!-- &lt;!&ndash; 配置Spring配置文件名称和位置 &ndash;&gt;-->
<!-- <context-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value>classpath:spring-persist-*.xml</param-value>-->
<!-- </context-param>-->

<!-- 需要将所有的spring配置文件都给dispatcherServlet加载 -->

<!-- filter-name必须是springSecurityFilterChain -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

3. 对Security提供的User类进行扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SecurityAdmin extends User {
private static final long serialVersionUID = 1L;
private Admin originalAdmin; // 自定义的登录的用户相关的类

public SecurityAdmin(Admin originalAdmin, List<GrantedAuthority> authorities){
super(originalAdmin.getLoginAcct(), originalAdmin.getUserPswd(), authorities);
this.originalAdmin = originalAdmin;
}

public Admin getOriginalAdmin() {
return originalAdmin;
}
}

4. 自定义类继承UserDetailsService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private AdminService adminService;
@Autowired
private RoleService roleService;
@Autowired
private AuthService authService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Admin admin = adminService.getAdminByLoginAcct(username);
Integer adminId = admin.getId();
List<Role> assignedRoleList = roleService.getAssignedRole(adminId);
List<String> authNameList = authService.getAssignedAuthNameByAdminId(adminId);
List<GrantedAuthority> authorities = new ArrayList<>();
assignedRoleList.forEach(role-> authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()))); // 注意角色名前面必须加"ROLE_"
authNameList.forEach(authName->authorities.add(new SimpleGrantedAuthority(authName)));
return new SecurityAdmin(admin, authorities);
}
}

5. 配置SpringSecurity配置类

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
@Configuration
@EnableWebSecurity // 启用Web安全功能
public class WebAppSecurityConfig {
@Autowired
private UserDetailsService userDetailsService;

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}



@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeRequests() // 对请求进行授权
.antMatchers("/index.html", "/admin/login.html", "/static/**") //针对指定页面进行授权
.permitAll() // 可以无条件访问
.and()
.antMatcher("/**")
.authorizeRequests(authorize->authorize.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.userDetailsService(userDetailsService)
.formLogin()
.loginPage("/admin/login.html") // 指定登录页面
.loginProcessingUrl("/admin/login") // 指定处理登录请求的地址
.defaultSuccessUrl("/admin/main.html") // 登录成功后前往的地址
.usernameParameter("loginAcct") // 账号请求参数名
.passwordParameter("loginPwd") // 密码请求参数名
.and()
.logout()
.logoutUrl("/admin/logout")
.logoutSuccessUrl("/admin/login.html")
.and()
.csrf()
.disable()
.build();
}
}

6. 通过配置类或注解进行权限控制

通过配置类进行权限控制

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
@Configuration
@EnableWebSecurity // 启用Web安全功能
public class WebAppSecurityConfig {
@Autowired
private UserDetailsService userDetailsService;

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}



@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeRequests()
.antMatchers("/index.html", "/admin/login.html", "/static/**")
.permitAll()
.and()
.authorizeRequests(authorize->
authorize.mvcMatchers("/admin/user.html").hasRole("经理")) // 表示访问/admin/user.html这个页面的用户必须有"经理"这个角色,注意这里的经理前面不需要加"ROLE_
.antMatcher("/**")
.authorizeRequests(authorize->authorize.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.userDetailsService(userDetailsService)
.formLogin()
.loginPage("/admin/login.html")
.loginProcessingUrl("/admin/login")
.defaultSuccessUrl("/admin/main.html")
.usernameParameter("loginAcct")
.passwordParameter("loginPwd")
.and()
.logout()
.logoutUrl("/admin/logout")
.logoutSuccessUrl("/admin/login.html")
.and()
.csrf()
.disable()
.build();
}
}

通过注解进行权限控制

  1. 在配置类上加@EnableGlobalMethodSecurity(prePostEnabled = true)

    1
    2
    3
    4
    @Configuration
    @EnableWebSecurity // 启用Web安全功能
    @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启注解权限控制
    public class WebAppSecurityConfig {
  2. 在要进行权限控制的类上加注解@PreAuthorize("表达式")

    以角色形式进行控制:

    1
    2
    3
    @PreAuthorize("hasRole('部长')")
    @PostMapping("/admin/role/page.json")
    public ResultEntity<PageInfo<Role>> getPageInfo(){

    或者以权限的形式进行权限控制:

    1
    2
    3
    @PreAuthorize("hasAuthority('user:add')")
    @PostMapping("/admin/add")
    public String addAdmin(Admin admin){

    或者两个都用:

    1
    2
    3
    @PreAuthorize("hasRole('经理') or hasAuthority('user:add')")
    @PostMapping("/admin/add")
    public String addAdmin(Admin admin){

其它的一些注解:

  • @PostAuthorize:先执行方法然后根据方法返回值判断是否具备权限;如查询一个用户对象,在@PostAuthorize注解中和当前登录的用户对象进行比较,如果不一致,则判断为不能访问,实现只能“自己查自己”的效果。(@PostAuthorize("returnObject.data.loginAcct==principal.username"):使用returnObject获得方法返回值,使用principal获得当前登录用户的主体对象)
  • @PreFilter:在方法执行前对传入的参数进行过滤,只能对集合类型的数据进行过滤,如@PreFilter(value="filterObject%2==0")
  • @PostFilter:在方法执行后对方法返回值进行过滤,只能对集合类型的数据进行过滤