权限管理过程中的相关概念
主体(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
| <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-*.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
<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()))); 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 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 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("经理")) .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(); } }
|
通过注解进行权限控制
在配置类上加@EnableGlobalMethodSecurity(prePostEnabled = true)
1 2 3 4
| @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebAppSecurityConfig {
|
在要进行权限控制的类上加注解@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
:在方法执行后对方法返回值进行过滤,只能对集合类型的数据进行过滤