继续上一章,给在内存中创建两个用户配置权限。配置权限有两种方式:
//哪个写在后面哪个起作用
//角色变成权限后会加一个ROLE_前缀,比如ROLE_teacher
UserDetails user2 = User.builder()
.username("thomas")
.password(passwordEncoder().encode("123456"))
.authorities("teacher:add","teacher:update")
.roles("teacher")
.build();
UserDetails user2 = User.builder()
.username("thomas")
.password(passwordEncoder().encode("123456"))
.roles("teacher")
.authorities("teacher:add","teacher:update")
.build();
以上两种写法,顺序不同,获取当前登录用户时,得到的权限值也不一样。总结就是:
当然,从代码层来说,角色和权限并没太大区别,并特别是在Spring Security中。
未做授权时,默认登录成功的用户可以访问所有资源(调任意一个接口),但有的接口只能允许管理员调用,因此,这里需要再实现授权功能。先看针对URL授权,即哪些权限可以访问哪些URL。新建配置类MyWebSecurityConfig,继承抽象类WebSecurityConfigurerAdapter,重写configure(HttpSecurity http)方法
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //授权请求
.anyRequest() //任何请求
.denyAll(); //拒绝所有请求访问
//.permitAll(); //允许所有请求
}
}
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //授权请求
.anyRequest() //任何请求
.denyAll(); //拒绝所有请求访问
//.permitAll(); //允许所有请求
http.formLogin().permitAll(); //放开表单登录
}
}
针对不同的url,要求拥有不同的权限才能访问,实现如下:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/student/**")
.hasAnyAuthority("ROLE_student", "ROLE_teacher") //有任一权限就能访问上面的url,形参是可变长字符串
.mvcMatchers("/teacher/**")
.hasAuthority("ROLE_teacher") //必须有这个权限才能访问上面的url
.anyRequest() //任何请求
.authenticated(); //都需要登录,即那些没有单独设置权限的url,仅需登录就能访问
http.formLogin().permitAll();
}
}
关于URL匹配,可选框架中的以下方法:
关于校验是否有对应的权限,框架中的方法可选:
还可以.access()
写表达式:
.mvcMatchers("/admin/**")
.access("hasRole('teacher') or hasAuthority('admin:query')")
//里面用单引号,省的转义
上面是URL级别的授权,接下来进行方法级别的权限控制。先写一个增删改查的简单代码,方便后面测试。
测试素材代码:
//新建教师接口
public interface TeacherService {
String add();
String update();
String delete();
String query();
}
//实现接口
@Service
@Slf4j
public class TeacherServiceImpl implements TeacherService {
@Override
public String add() {
log.info("添加教师成功");
return "添加教师成功";
}
@Override
public String update() {
log.info("修改教师成功");
return "修改教师成功";
}
@Override
public String delete() {
log.info("删除教师成功");
return "删除教师成功";
}
@Override
public String query() {
log.info("查询教师成功");
return "查询教师成功";
}
}
简单补充下controller:
@RestController
@RequestMapping("/teacher")
public class TeacherController {
@Resource
private TeacherService teacherService;
@GetMapping("/query")
public String queryInfo() {
return teacherService.query();
}
@GetMapping("/add")
public String addInfo() {
return teacherService.add();
}
@GetMapping("/update")
public String updateInfo() {
return teacherService.update();
}
@GetMapping("/delete")
public String deleteInfo() {
return teacherService.delete();
}
}
配置类中新建三个测试用户在内存中:
@Configuration
public class MySecurityUserConfig {
@Bean
public UserDetailsService userDetailService() {
UserDetails user1 = User.builder()
.username("liu")
.password(passwordEncoder().encode("123456"))
.roles("student")
.build();
UserDetails user2 = User.builder()
.username("Mr.liu")
.password(passwordEncoder().encode("123456"))
.roles("teacher")
.build();
UserDetails user3 = User.builder()
.username("admin")
.password(passwordEncoder().encode("123456"))
.authorities("teacher:add", "teacher:update")
.build();
//创建两个用户
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(user1);
userDetailsManager.createUser(user2);
return userDetailsManager;
}
/*
* 从 Spring5 开始,强制要求密码要加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
//使用加密算法对密码进行加密
return new BCryptPasswordEncoder();
}
}
Web安全配置适配器类:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//任何访问均需要认证
http.authorizeRequests().anyRequest().authenticated();
http.formLogin().permitAll();
}
}
进行方法级别的控制
首先,加上启动全局方法安全的注解 @EnableGlobalMethodSecurity(prePostEnabled = true)
//@Configuration
//@EnableGlobalMethodSecurity注解中有@Configuration注解,所以这里注掉了就
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//任何访问均需要认证
http.authorizeRequests().anyRequest().authenticated();
//登录放行
http.formLogin().permitAll();
}
}
@EnableGlobalMethodSecurity(prePostEnabled = true)中的prePostEnabled是预授权和后授权,预授权即访问前判断有无权限,后授权则是方法执行完以后才判断是否有权限,后授权的使用场景比较少。接下来修改要控制访问的方法,使用 前置授权注解@PreAuthorize
@Service
@Slf4j
public class TeacherServiceImpl implements TeacherService {
@Override
@PreAuthorize("hasAuthority('teacher:add') OR hasRole('teacher')")
public String add() {
log.info("添加教师成功");
return "添加教师成功";
}
@Override
@PreAuthorize("hasAuthority('teacher:update')")
public String update() {
log.info("修改教师成功");
return "修改教师成功";
}
@Override
@PreAuthorize("hasAuthority('teacher:delete')")
public String delete() {
log.info("删除教师成功");
return "删除教师成功";
}
@Override
@PreAuthorize("hasRole('teacher')")
public String query() {
log.info("查询教师成功");
return "查询教师成功";
}
}
此时,登录有不同权限的不同角色,其只能访问对应有权限的方法。
注意,这里控制的是对方法的访问,仅仅是限制对方法的访问。
@GetMapping("/delete")
public String deleteInfo() {
return teacherService.delete();
}
改为:
@GetMapping("/delete")
public String deleteInfo() {
int a = 10
log.info("进入了TeacherController,a={}" , a);
return teacherService.delete();
}
登录学生账户,访问delete接口,此时结果仍然403,但控制台可以看到在到达被限制权限的方法前的代码是可以访问的:
当然,@PreAuthorize注解也可以写在Controller中的方法上,此时上面的int a = 10自然就访问不到了。
小总结: