一. 授权流程原理

1.授权流程描述

授权一定是在认证通过之后,授权流程是通过FilterSecurityInterceptor拦截器来完成,FilterSecurityInterceptor通过调用SecurityMetadataSource来获取当前访问的资源所需要的权限,然后通过调用AccessDecisionManager投票决定当前用户是否有权限访问当前资源。授权流程如下

RBAC传统授权流程:

spring security oauth2授权认证服务器搭建 springsecurity授权流程_spring

Security授权流程:

spring security oauth2授权认证服务器搭建 springsecurity授权流程_User_02

spring security oauth2授权认证服务器搭建 springsecurity授权流程_java_03

①在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不能省略,括号中是单引号。