一. 授权流程原理
1.授权流程描述
授权一定是在认证通过之后,授权流程是通过FilterSecurityInterceptor拦截器来完成,FilterSecurityInterceptor通过调用SecurityMetadataSource来获取当前访问的资源所需要的权限,然后通过调用AccessDecisionManager投票决定当前用户是否有权限访问当前资源。授权流程如下
RBAC传统授权流程:
Security授权流程:
①在FilterSecurityInterceptor中会调用其父类AbstractSecurityInterceptor
的beforeInvocation方法做授权之前的准备工作
②该方法中通过SecurityMetadataSource…getAttributes(object);获得资源所需要的访问权限 ,通过SecurityContextHolder.getContext().getAuthentication()获取当前认证用户的认证信息,即Authentication对象 。
③然后通过调用AccessDecisionManager.decide(authenticated, object, attributes);进行授权,该方法使用了投票机制来决定用户是否有资源访问权限
AccessDecisionManager接口有三个实现类,他们通过通过AccessDecisionVoter投票
器完成投票,三种投票策略如下:
AffirmativeBased : 只需有一个投票赞成即可通过
ConsensusBased:需要大多数投票赞成即可通过,平票可以配置
UnanimousBased:需要所有的投票赞成才能通过
而投票器也有很多,如RoleVoter通过角色投票,如果ConfigAttribute是以“ROLE_”开头的,则将使用RoleVoter进行投票,AuthenticatedVoter 是用来区分匿名用户、通过Remember-Me认证的用户和完全认证的用户(登录后的)
④投票通过,放心请求响应的资源
2.Web授权
web授权API说明
在Security配置类中,可以通过HttpSecurity.authorizeRequests()给资源指定访问的权限,其API如下:
①anyRequest():任何请求
②antMatchers(“/path”) :匹配某个资源路径
③authenticationed() : 保护URL需要登录访问anyRequest().authenticationed()
④permitAll():指定url无需保护(放行)一般用户静态资源antMatchers(“/path”).permitAll()
⑤hasRole(String role):某个资源需要用户拥有什么样的role才能访问
antMatchers(“/path”).hasRole(“admin”)
⑥hasAuthority(String authority):某个资源需要用户拥有什么样的权限才能访问
antMatchers(“/path”).hasAuthority(“admin”)
⑦hasAnyRole(String …roles):某个资源拥有指定角色中的一个就能访问
⑧hasAnyAuthority(String … authorities):某个资源拥有指定权限中的一个就能访问
⑨access(String attribute):该方法使用SPEL表达式,可以创建复杂的限制
⑩hasIpAddress(String ip):拥有什么样的ip或子网可以访问该资源
授权规则注意
我们通常把细节的规则设置在前面,范围比较大的规则设置放在后面,返例:如有以下配置
.antMatchers("/admin/**").hasAuthority(“admin”)
.antMatchers("/admin/login").permitAll();
那么第二个权限规则将不起作用,因为第一个权限规则覆盖了第二个权限规则
因为权限的设置是按照从上到下的优先级。及满足了最开始的权限设置,那么后面的设置就不起作用了。
3.Web授权实战
我们这一次在入门案例的基础上进行修改,所有的认证数据,授权数据都从数据库进行获取
①准备数据库和Domain,mapper等
t_user //用户登录表
t_user_role //用户和角色中间表
t_permisstion //权限表
t_role //角色表
t_role_permission //角色和权限中间表
②编写controller
@RestController
public class DeptController {
@RequestMapping("/dept/list")
public String list(){
return "dept.list";
}
@RequestMapping("/dept/add")
public String add(){
return "dept.add";
}
@RequestMapping("/dept/update")
public String update(){
return "dept.update";
}
@RequestMapping("/dept/delete")
public String delete(){
return "dept.delete";
}
}
---------------------------------------------------------
@RestController
public class EmployeeController {
@RequestMapping("/employee/list")
public String list(){
return "employee.list";
}
@RequestMapping("/employee/add")
public String add(){
return "employee.add";
}
@RequestMapping("/employee/update")
public String update(){
return "employee.update";
}
@RequestMapping("/employee/delete")
public String delete(){
return "employee.delete";
}
}
方法上的requestmapping就对应了权限表t_permission的资源
③配置HttpSecurity
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启授权
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//提供用户信息,这里没有从数据库查询用户信息,在内存中模拟
/*@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager inMemoryUserDetailsManager =
new InMemoryUserDetailsManager();
inMemoryUserDetailsManager.createUser(User.withUsername("zs").password("123").authorities("admin").build());
return inMemoryUserDetailsManager;
}*/
//密码编码器:不加密(权限框架提供了设置密码加密方式的配置对象)
@Bean
public PasswordEncoder passwordEncoder(){
// return NoOpPasswordEncoder.getInstance();//不加密
return new BCryptPasswordEncoder();//加密
}
//授权规则配置
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //授权配置
.antMatchers("/login","/login.html").permitAll() //登录路径放行
.anyRequest().authenticated() //其他路径都要认证之后才能访问
.and().formLogin() //允许表单登录
.loginPage("/login.html") //这个就是自定义的登录页面
.loginProcessingUrl("/login") //告诉框架,现在的登录请求的URL地址是:/login
.successForwardUrl("/loginSuccess") // 设置登陆成功页
//.successHandler(new 、MyAuthenticationSuccessHandler())//设置认证成功后,handler的处理器
//.failureHandler(new MyAuthenticationFailureHandler())//设置认证失败后handler的处理器
.and().logout().permitAll() //登出路径放行 /logout。这是框架自带的登出请求
.and().csrf().disable(); //关闭跨域伪造检查
④.修改UserDetailService加载用户权限
package cn.itsource.th.userService;
import cn.itsource.th.domain.Permission;
import cn.itsource.th.domain.VipUser;
import cn.itsource.th.mapper.VipUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private VipUserMapper vipUserMapper;
@Override//根据参数中的用户名,来查询用户的对象信息
//1.整个方法体中,并没有对前端传过来的密码进行校验。
//2.前端传过来的用户密码,后台代码根本就没有提供获取的方法。
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
VipUser vipUser = vipUserMapper.findUserByUserName(userName);
List<GrantedAuthority> authorityList = new ArrayList<>();//在传入时,会同时查询出当前用户的权限列表
//查询当前用户所有的权限
//在security进行授权管理时,就要在这里将当前登录用户的权限列表给查出来
List<Permission> permissionList = vipUserMapper.findPermissionListByUserName(userName);
for (Permission permission:permissionList) {
//将查询来的权限逐一添加到authorityList中
authorityList.add(new SimpleGrantedAuthority(permission.getExpression()));
}
//密码最终校验的地方是由Security框架完成。 原始密码保存在SecurityContext上下文,传入一个加密之后的密码:vipUser.getPassword()
return new User(userName, vipUser.getPassword(),authorityList);
}
}
登录测试(此时没有控制权限,都能访问)
合理分配用户的权限,登录测试对于不同的资源是否应该有对应的访问权限
4.@PreAuthorize(常用的授权方法)
①开启@PreAuthorize授权支持
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled= true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
②使用@PreAuthorize进行方法授权
@RestController
public class EmployeeController {
//zs有这个访问权限,但李四没有
@RequestMapping("/employee/list")
@PreAuthorize("hasAuthority('employee:list')")
public String list(){
return "employee.list";
}
}
@PreAuthorize("hasAuthority('employee:list')")
还可以同时添加多个
@PreAuthorize("hasAnyAuthority('employee:add','employee:update')")
指明了方法必须要有 employee:add 或者 employee:update的权限才能访问 ,
该注解不需要有固定的前缀。注意格式“@PreAuthorize("hasAuthority('employee:add')")” ,
hasAuthority不能省略,括号中是单引号。