1 需求
现有一个编写好的系统,需要实现用户登录验证即可,同时根据用户的权限来限制用户可以访问的接口
2 编写SpringSecurity配置类
继承 WebSecurityConfigurerAdapter 类并重写configure 方法
package cn.xiangxu.spring_security_system; //import cn.xiangxu.spring_security_system.service.MyUserService; import cn.xiangxu.spring_security_system.utils.MyPasswordEncoder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; /** * 权限服务类 */ @Configuration // 等同于XML中的beans @EnableWebSecurity // 开启webSecurity功能 public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { // @Autowired // private MyUserService myUserService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 直接将用户信息和权限写死 auth.inMemoryAuthentication() .withUser("admin") .password("123456") .roles("ADMIN"); auth.inMemoryAuthentication() .withUser("wys") .password("123456") .roles("ADMIN"); auth.inMemoryAuthentication() .withUser("demo") .password("123456") .roles("USER"); // auth.userDetailsService(myUserService).passwordEncoder(new MyPasswordEncoder()); // 利用自定义的UserService进行管理 } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/").permitAll() // 主页面请求拦截排除 .anyRequest().authenticated() // 除主页面外的所有请求都会被拦截 .and() .logout().permitAll() // 注销请求拦截排除 .and() .formLogin(); // 设置使用表单登录的方式 http.csrf().disable(); // 关闭csrf验证 } @Override public void configure(WebSecurity web) throws Exception { // 拦截排除设置 web.ignoring().antMatchers("/js/**", "/css/**", "/images/**"); } }
坑01:由于从springboot2.0.3开始使用的springsecurity的版本是5.x,这个版本需要提供一个PasswordEncoder的实例,否则就会如下错误
解坑01之方法01:只需要创建一个PasswordEncoder的Bean并被spring容器所管理即可
》创建一个实现了PasswordEncoder接口的实现类MyPasswordEncoder,在encode方法中编写加密算法,在matches方法中编写匹配算法
》在配置文件中配置MyPasswordEncoder对应的Bean即可(在一个标注了@Configuration注解的类中添加下面的逻辑即可)
解坑01之方法02:在创建内存用户的时候指定一个PasswordEncoder实例
》创建一个实现了PasswordEncoder接口的实现类MyPasswordEncoder,在encode方法中编写加密算法,在matches方法中编写匹配算法
》创建内存用户的时候直接指定PasswordEncoder即可
3 编写一些接口用于测试
package cn.xiangxu.spring_security_system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController //@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启@PreAuthorize注解 public class SpringSecuritySystemApplication { public static void main(String[] args) { SpringApplication.run(SpringSecuritySystemApplication.class, args); } @GetMapping(value = "/") public String home() { return "Welcome to study springSecurity."; } @GetMapping(value = "/hello") public String hello() { return "hello boy"; } // @PreAuthorize("hasRole('ROLE_ADMIN')") // 设定权限校验:只用ADMIN角色才能调用该接口 @GetMapping("/roleAuth") public String role() { return "admin role"; } }
4 请求测试接口
利用硬编码的用户名和密码进行登录
5 设置权限校验
5.1 在控制类上添加 @EnableGlobalMethodSecurity 注解
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.security.config.annotation.method.configuration; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({GlobalMethodSecuritySelector.class}) @EnableGlobalAuthentication @Configuration public @interface EnableGlobalMethodSecurity { boolean prePostEnabled() default false; boolean securedEnabled() default false; boolean jsr250Enabled() default false; boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default 2147483647; }
@EnableGlobalMethodSecurity(prePostEnabled = true)
代码解释:
整个项目启用方法角色验证注解,prePostEnabled = true 表示启用@PreAuthorize 和 @PostAuthorize 两个方法角色验证注解(@PreFilter() 和 @PostFilter() 也会被启用)
5.2 在需要进行角色验证的接口上方添加 @PreAuthorize 注解
@PreAuthorize("hasRole('ROLE_ADMIN')")
代码解释:
有该注解的方法只有角色是ADMIN的用户才可以访问
package cn.xiangxu.spring_security_system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启@PreAuthorize注解 public class SpringSecuritySystemApplication { public static void main(String[] args) { SpringApplication.run(SpringSecuritySystemApplication.class, args); } @GetMapping(value = "/") public String home() { return "Welcome to study springSecurity."; } @GetMapping(value = "/hello") public String hello() { return "hello boy"; } @PreAuthorize("hasRole('ROLE_ADMIN')") // 设定权限校验:只用ADMIN角色才能调用该接口 @GetMapping("/roleAuth") public String role() { return "admin role"; } }
6 权限校验注解
6.1 @PreAuthorize
在执行方法之前进行权限校验
6.1.1 角色验证
例如:@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") // ADMIN 和 USER 角色都有权限调用这个接口
package cn.xiangxu.spring_security_system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启@PreAuthorize注解 public class SpringSecuritySystemApplication { public static void main(String[] args) { SpringApplication.run(SpringSecuritySystemApplication.class, args); } @GetMapping(value = "/") public String home() { return "Welcome to study springSecurity."; } @GetMapping(value = "/hello") public String hello() { return "hello boy"; } @PreAuthorize("hasRole('ROLE_ADMIN')") // 设定权限校验:只用ADMIN角色才能调用该接口 @GetMapping("/roleAuth") public String role() { return "admin role"; } @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") // ADMIN 和 USER 角色都有权限调用这个接口 @GetMapping("/test01") public String rest01() { return "test01"; } }
6.1.2 参数验证01
例如:@PreAuthorize("#id < 10")
代码解释: 方法必须有id参数,而且参数的值必须小于10
package cn.xiangxu.spring_security_system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启@PreAuthorize注解 public class SpringSecuritySystemApplication { public static void main(String[] args) { SpringApplication.run(SpringSecuritySystemApplication.class, args); } @GetMapping(value = "/") public String home() { return "Welcome to study springSecurity."; } @GetMapping(value = "/hello") public String hello() { return "hello boy"; } @PreAuthorize("hasRole('ROLE_ADMIN')") // 设定权限校验:只用ADMIN角色才能调用该接口 @GetMapping("/roleAuth") public String role() { return "admin role"; } @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") // ADMIN 和 USER 角色都有权限调用这个接口 @GetMapping("/test01") public String rest01() { return "test01"; } @PreAuthorize("#id < 10") @GetMapping("/test02/{id}") public String test02(@PathVariable("id") Integer id) { return "test02 -> 获取到的ID为:" + id; } }
6.1.3 参数验证03
例如:@PreAuthorize("principal.username.equals(#username)")
代码解释:方法必须有username参数,而且参数的值必须是登录用户的用户名
package cn.xiangxu.spring_security_system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启@PreAuthorize注解 public class SpringSecuritySystemApplication { public static void main(String[] args) { SpringApplication.run(SpringSecuritySystemApplication.class, args); } @GetMapping(value = "/") public String home() { return "Welcome to study springSecurity."; } @GetMapping(value = "/hello") public String hello() { return "hello boy"; } @PreAuthorize("hasRole('ROLE_ADMIN')") // 设定权限校验:只用ADMIN角色才能调用该接口 @GetMapping("/roleAuth") public String role() { return "admin role"; } @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") // ADMIN 和 USER 角色都有权限调用这个接口 @GetMapping("/test01") public String rest01() { return "test01"; } @PreAuthorize("#id < 10") @GetMapping("/test02/{id}") public String test02(@PathVariable("id") Integer id) { return "test02 -> 获取到的ID为:" + id; } @PreAuthorize("principal.username.equals(#username)") @GetMapping("/test03") public String test03(@RequestParam("username") String username) { return "test03 -> 获取到的username为:" + username; } }
6.2 @PostAuthorize
在执行方法之后进行权限验证
例如:@PostAuthorize("returnObject.equals('test04')")
代码解释:判断方法的返回值是否等于 test04 , 如果是就通过验证,如果不是就验证失败
技巧:returnObject代表方法的返回对象
package cn.xiangxu.spring_security_system; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启@PreAuthorize注解 public class SpringSecuritySystemApplication { public static void main(String[] args) { SpringApplication.run(SpringSecuritySystemApplication.class, args); } @GetMapping(value = "/") public String home() { return "Welcome to study springSecurity."; } @GetMapping(value = "/hello") public String hello() { return "hello boy"; } @PreAuthorize("hasRole('ROLE_ADMIN')") // 设定权限校验:只用ADMIN角色才能调用该接口 @GetMapping("/roleAuth") public String role() { return "admin role"; } @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_USER')") // ADMIN 和 USER 角色都有权限调用这个接口 @GetMapping("/test01") public String rest01() { return "test01"; } @PreAuthorize("#id < 10") @GetMapping("/test02/{id}") public String test02(@PathVariable("id") Integer id) { return "test02 -> 获取到的ID为:" + id; } @PreAuthorize("principal.username.equals(#username)") @GetMapping("/test03") public String test03(@RequestParam("username") String username) { return "test03 -> 获取到的username为:" + username; } @PostAuthorize("returnObject.equals('test04')") @GetMapping(value = "/test04") public String test04(@RequestParam("info") String info) { return info; } }
6.3 @PreFilter
在方法执行前对列表型的参数进行一些验证
例如:@PreFilter("filterObject%2==0")
·代码解释:列表型的参数中的每个元素必须能被2整除
技巧:filterObject 代表列表型的参数
6.4 @PostFilter("filterObject%4 == 0")
在方法执行后对返回的列表型对象进行一些验证
例如:@PostFilter("filterObject%4 == 0")
代码解释:列表型的返回对象中的每个元素必须能被4整除
技巧:filterObject 代表列表型的返回对象