框架解决问题
本质上是过滤器链,使用这些过滤器解决: 1、用户认证 检验用户是否为系统中的合法主体。
2、用户授权 检验某位用户是否有权限执行某项操作。
使用
一般会搭配SpringBoot使用
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>{spring-security-version}</version>
</dependency>
初步使用
写一个简单的controller,直接返回String 就行 这时候去登录,在前端网页会出现要登录的页面 Spring Security默认用户user,默认生成随机密码在控制台
自定义开发
用户名+密码
自定义类继承UsernamePasswordAuthenticationFilter过滤器,重写attemptAuthentication、successfulAuthentication、unsuccessfulAuthentication方法。 在attemptAuthentication方法中做校验,成功则调用successfulAuthentication方法、失败则调用unsuccessfulAuthentication方法。
注意 做认证差数据库的操作,需要写在实现UserDetailsService接口的类中
步骤
1、创建类继承UsernamePasswordAuthenticationFilter,重写三个方法。 2、创建类实现UserDetailsService接口,编写查询数据过程,返回User对象(这里的User对象是安全框架的)
当返回的User对象密码数据需要加密的时候:使用PasswordEncoder接口: new一个BCryptPasswordEncoder,使用它的encode方法完成加密。 还需要在@Configuration+@Bean注册一个BCryptPasswordEncoder实例对象进Spring容器
示例:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){return new BCryptPasswordEncoder();}
}
@Service
public class LoginService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在!");
}
String pwd ="$2a$10$2R/M6iU3mCZt3ByG7kwYTeeW0w7/UqdeXrb27zkBIizBvAven0/na";
return new User(username,pwd, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,"));
}
}
示例
从数据库中查用户名和密码,来完成登录校验
依赖:SpringBoot、Spring Security、MyBatisPlus、MySQL、lombok
配置文件application.properties
#mysql 数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/demo?serverTimezone=GMT%2B8 spring.datasource.username=root
spring.datasource.password=root
实体类entity
@Data
public class Users {
private Integer id;
private String username;
private String password;
}
登录实现类Service
@Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
QueryWrapper<Users> wrapper = new QueryWrapper();
wrapper.eq("username",s);
Users users = usersMapper.selectOne(wrapper);
if(users == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
System.out.println(users);
List<GrantedAuthority> auths =AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
return new User(users.getUsername(),new BCryptPasswordEncoder().encode(users.getPassword()),auths);
}
}
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){return new BCryptPasswordEncoder();}
}
配置需要认证的路径/登录页面
配置类修改为如下:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){return new BCryptPasswordEncoder();}
@Override
protected void configure(HttpSecurity http)throws Exception{
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/success").permitAll()
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()
.anyRequest().authenticated()
.and().csrf().desable();
}
}
用户授权
基于角色或权限进行访问控制
hasAuthority方法
当前主体有权限时返回true 无权限返回false
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){return new BCryptPasswordEncoder();}
@Override
protected void configure(HttpSecurity http)throws Exception{
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/success").permitAll()
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()
.andMatchers("/test/index").hasAuthority("admin")
.anyRequest().authenticated()
.and().csrf().desable();
}
}
hasAnyAuthority方法
当前的主体有任何提供的角色(给定的作为一个逗号分隔的字符串列表)的话,返回true 配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){return new BCryptPasswordEncoder();}
@Override
protected void configure(HttpSecurity http)throws Exception{
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/success").permitAll()
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()
.andMatchers("/test/index").hasAnyAuthority("admin,manager")
.anyRequest().authenticated()
.and().csrf().desable();
}
}
hasRole方法
如果用户具备给定角色就允许访问,否则出现403。类似hasAuthority方法。 对应部分改为:.andMatchers("/test/index").hasRole("admin")//用户需要带有admin权限
注意不同点在于,userDetailService返回的角色需要ROLE_xxx(有前缀),而hasRole方法参数则为xxx。
hasAnyRole方法
类似hasAnyAuthority方法。
配置无权限时跳转页面
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){return new BCryptPasswordEncoder();}
@Override
protected void configure(HttpSecurity http)throws Exception{
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/success").permitAll()
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()
.andMatchers("/test/index").hasAnyAuthority("admin,manager")
.anyRequest().authenticated()
.and().csrf().desable();
}
}
用户授权(注解使用)
@Secured
判断是否具有角色,另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。 使用注解先要开启注解功能,在 启动类或配置类 上添加@EnableGlobalMethodSecurity(securedEnabled=true) 注解
在controller层的方法上面加上@Secured 注解
@RequestMapping("testSecured")
@ResponseBody
@Secured({"ROLE_normal","ROLE_admin"})
public String helloUser() {
return "hello,user";
}
@PreAuthorize
注解适合进入方法前的权限验证, @PreAuthorize 可以将登录用户的 roles/permissions 参数传到方法中。使用注解先要开启注解功能,在 启动类或配置类 添加@EnableGlobalMethodSecurity(prePostEnabled = true)
在controller层的方法上面加上@PreAuthorize 注解
@RequestMapping("/preAuthorize") @ResponseBody
@PreAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("preAuthorize"); return "preAuthorize";
}
@PostAuthorize
注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值 的权限。启动类开启@EnableGlobalMethodSecurity(prePostEnabled = true)
在controller层的方法上面加上@PostAuthorize 注解
@RequestMapping("/testPostAuthorize")
@ResponseBody @PostAuthorize("hasAnyAuthority('menu:system')")
public String preAuthorize(){
System.out.println("test--PostAuthorize"); return "PostAuthorize";
}
@PostFilter
权限验证之后对返回前端的数据进行过滤,留下用户名是 admin1 的数据,表达式中的 filterObject 引用的是方法返回值 List 中的某一个元素
controller层方法
@RequestMapping("getAll")
@PreAuthorize("hasRole('ROLE_管理员')")
@PostFilter("filterObject.username == 'admin1'")
@ResponseBody
public List<UserInfo> getAllUser(){
ArrayList<UserInfo> list = new ArrayList<>();
list.add(new UserInfo(1l,"admin1","6666"));
list.add(new UserInfo(2l,"admin2","888"));
return list;
}
@PreFilter
进入控制器之前对前端传入数据进行过滤
controller层方法
@RequestMapping("getTestPreFilter")
@PreAuthorize("hasRole('ROLE_管理员')")
@PreFilter(value = "filterObject.id%2==0")
@ResponseBody
public List<UserInfo> getTestPreFilter(@RequestBody List<UserInfo> list){
list.forEach(t-> { System.out.println(t.getId()+"\t"+t.getUsername());
});
return list;
}
用户注销
用户登录后如何进行退出操作,需要在配置类中添加退出映射地址http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll()
配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception{
auth.userDetailsService(userDetailsService).passwordEncoder(password());
}
@Bean
PasswordEncoder password(){return new BCryptPasswordEncoder();}
@Override
protected void configure(HttpSecurity http)throws Exception{
http.logout().logoutUrl("/logout").logoutSuccessUrl("/index").permitAll();
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/success").permitAll()
.and().authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()
.andMatchers("/test/index").hasAnyAuthority("admin,manager")
.anyRequest().authenticated()
.and().csrf().desable();
}
}
自动登录
实现效果:需要认证才能访问的页面,在关闭浏览器再次打开后还能不能录直接访问。
原理:把cookie和对应用户信息存入数据库
重点
单点登录
|