SpringSecurity学习笔记
作者:互联网
1、SpringSecurity学习笔记
SpringSecurity底层本质是一个过滤器链
FilterSecurityInterceptor:是一个方法级的过滤器,位于过滤器链的最底部
ExceptionTranslationFilter:异常过滤器,用来处理认证授权过程中抛出的异常
UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中的用户名和密码。
1、两个重要接口
1、UserDetailsService:从数据库查询用户名和密码的过程
-
创建类继承UsernamePasswordAuthenticationFilter,重写它的三个方法
-
创建类实现UserDetailsService,编写查询数据库的过程,返回一个User对象,这个User对象是SpringSecurity提供的一个对象
2、PasswordEncoder:数据加密的接口,一般用于返回User对象密码的加密
2、认证
1、设置登录用户名和密码
-
第一种:application.yml配置文件中配置
spring:
security:
user:
name: admin
password: admin123 -
第二种:通过配置类配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 对密码进行加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password = encoder.encode("admin123");
auth.inMemoryAuthentication()
// 登录的用户名
.withUser("admin")
// 登录的密码
.password(password)
// 角色
.roles("admin");
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
} -
第三种:自定义编写实现类
-
编写UserDetailsService的实现类
package com.zq.security.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new User("admin",passwordEncoder.encode("admin123"),authorities);
}
}
-
编写配置类使用我们自定义的UserDetailsService
package com.zq.security.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class CustomerSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
-
2、通过数据库完成用户认证
1、mybatis-plus依赖
<!--Mybatis-Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
2、实体类Users.java
import lombok.Data;
import java.io.Serializable;
@Data
public class Users implements Serializable {
private Integer id;
private String username;
private String password;
}
3、数据库接口UsersMapper.java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.security.pojo.Users;
public interface UsersMapper extends BaseMapper<Users> {
}
4、自定义UserDetailsService
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zq.security.mapper.UsersMapper;
import com.zq.security.pojo.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
@Autowired
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 根据表单传过来的用户名从数据库查询用户信息
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
Users users = usersMapper.selectOne(queryWrapper);
// 当用户名不存在时抛出异常
if (null == users){
throw new UsernameNotFoundException("用户名不存在");
}
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
// 将从数据库查询的用户名和密码封装到User并进行返回
return new User(users.getUsername(),encoder.encode(users.getPassword()),authorities);
}
}
5、config配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
3、自定义登录页面和白名单
1、config配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写登录页面
http.formLogin()
// 登录页面的路径
.loginPage("/login.html")
// 点击登录访问的路径
.loginProcessingUrl("/user/login")
// 登录成功后跳转的路径
.defaultSuccessUrl("/user/index").permitAll()
// 哪些访问路径不需要认证可以直接访问
.and().authorizeRequests()
.antMatchers("/hello","/user/login").permitAll()
// 任何路径都能访问
.anyRequest().authenticated()
// 关闭csrf防护
.and().csrf().disable();
}
}
3、授权
1、hasAuthority方法:当前用户具有指定的权限,有则返回true,否则返回false
-
第一步:配置类指定权限
// 表示当前用户访问这个路径必须有admin权限
.antMatchers("/user/index").hasAuthority("admin") -
第二步:返回User对象里面设置当前登录对象相对应的权限
// 给予当前用户admin的权限
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("admin");
return new User("username",new BCryptPasswordEncoder().encode("password"),authorities);
注意:此方法只能针对一个权限
2、hasAnyAuthority方法:当前用户具有指定权限的任意一种则返回true,否则返回false
-
配置类指定权限
// 只要有admin或root任意一个权限就能访问
.antMatchers("/user/index").hasAnyAuthority("admin","root")
3、hasRole方法:当前用户具有指定角色才能允许访问
-
第一步:配置类配置
// 当前登录用户必须拥有manage这个角色才能访问
.antMatchers("/user/index").hasRole("manage") -
第二步:hasRole()原码分析:
public ExpressionInterceptUrlRegistry hasRole(String role) {
return access(ExpressionUrlAuthorizationConfigurer.hasRole(role));
}
// 可以看出源码返回角色时默认加了前缀ROLE_
private static String hasRole(String role) {
Assert.notNull(role, "role cannot be null");
Assert.isTrue(!role.startsWith("ROLE_"),
() -> "role should not start with 'ROLE_' since it is automatically inserted. Got '" + role + "'");
return "hasRole('ROLE_" + role + "')";
} -
第三步:给登录角色配置权限
// 因为原码在角色的前面加了前缀ROLE_,所以我们在分配角色的时候也需要加上这个前缀
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_manage");
return new User(users.getUsername(),encoder.encode(users.getPassword()),authorities);
4、hasAnyRole方法:拥有任意一个角色才能访问
-
第一步:配置类配置
// 当前登录用户拥有manage或admin任意一个角色才能访问
.antMatchers("/user/index").hasAnyRole("admin","manage") -
第二步:给登录角色配置权限
List<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_admin");
return new User(users.getUsername(),encoder.encode(users.getPassword()),authorities);
4、自定义403没有权限访问页面
1、配置类中配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 没有权限时跳转的页面路径
http.exceptionHandling().accessDeniedPage("/unauth.html");
}
5、常用注解的使用
1、@Secured("ROLE_role1,ROLE_role1")
指定角色才能访问,使用在Controller的方法上
注意:使用该注解必须在主启动类上添加@EnableGlobalMethodSecurity(securedEnabled = true)
@GetMapping("/update")
@Secured({"ROLE_update","ROLE_sale"})
public String update(){
return "hello update";
}
2、@PreAuthorize("hasAnyAuthority('menu:system')")
注解进入方法前的权限验证,@PreAuthorize可以将登录用户的角色或权限传入方法中
注意:使用该注解必须在主启动类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)
@GetMapping("/insert")
// @PreAuthorize("hasRole('ROLE_管理员')")
@PreAuthorize("hasAnyAuthority('menu:system')")
public String insert(){
return "hello insert";
}
3、@PostAuthorize("hasAnyAuthority('menu:system')")
这个注解用的并不多,在方法执行后进行权限验证,适合验证带有返回值权限
注意:使用该注解必须在主启动类上添加@EnableGlobalMethodSecurity(prePostEnabled = true)
@GetMapping("/select")
@PostAuthorize("hasAnyAuthority('menu:normal')")
public String select(){
System.out.println("hello select");
return "hello select";
}
4、@PostFilter("filterObject.username == 'admin1'")
权限验证之后对返回的数据进行过滤,留下的是admin1的数据
表达式中filterObject引用的是方法返回值List中的某一个元素
@GetMapping("/list")
@PostFilter("filterObject.username == 'admin1'")
@PreAuthorize("hasRole('ROLE_管理员')")
public List<Users> list(){
List<Users> users = Arrays.asList(
new Users(1,"admin1","123"),
new Users(2,"admin2","456"),
new Users(1,"admin2","789"),
new Users(1,"admin1","321"),
new Users(1,"admin3","135")
);
return users;
}
5、@PreFilter("filterObject.id % 2 == 0")
权限验证之前对传入的数据进行过滤,当id是2的倍数的时候则留下
@GetMapping("/list2")
@PreFilter("filterObject.id % 2 == 0")
@PreAuthorize("hasRole('ROLE_sale')")
public List<Users> list2(List<Users> users){
return users;
}
6、用户注销
1、配置类中配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 用户注销
http.logout()
// 用户点注销访问的url
.logoutUrl("/logout")
// 注销成功跳转的url
.logoutSuccessUrl("/user/logout")
.permitAll();
}
7、基于数据库实现“记住我”
1、原理:
浏览器端:Cookie 储存 加密串
数据库端:加密串 与 用户信息字符串对应
认证时:Cookie 使用 加密串匹配用户信息进行认证
1、第一步:创建数据库表
CREATE TABLE persistent_logins (
username VARCHAR ( 64 ) NOT NULL,
series VARCHAR ( 64 ) PRIMARY KEY,
token VARCHAR ( 64 ) NOT NULL,
last_used TIMESTAMP NOT NULL)
2、配置类中进行配置:注入数据源,配置操作数据库对象
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 注入数据源
*/
@Autowired
private DataSource dataSource;
/**
* 将数据库操作对象交由spring容器管理
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl bean = new JdbcTokenRepositoryImpl();
bean.setDataSource(dataSource);
// 自动生成表
// bean.setCreateTableOnStartup(true);
return bean;
}
}
3、配置类进一步配置,设置记住我功能
关键代码:
// 记住我有效时长,以秒为单位 .tokenValiditySeconds(60) .userDetailsService(userDetailsService) // 关闭csrf防护 .and().csrf().disable();
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
/**
* 注入数据源
*/
@Autowired
private DataSource dataSource;
/**
* 将数据库操作对象交由spring容器管理
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl bean = new JdbcTokenRepositoryImpl();
bean.setDataSource(dataSource);
// 自动生成表
// bean.setCreateTableOnStartup(true);
return bean;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定义自己编写登录页面
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/user/login")
.defaultSuccessUrl("/user/index").permitAll()
// 开启记住我
.and().rememberMe().tokenRepository(persistentTokenRepository())
// 记住我有效时长,以秒为单位
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService)
// 关闭csrf防护
.and().csrf().disable();
}
}
4、登录页面添加=="记住我"==复选框
记住我:<input type="checkbox" name="remember-me" title="记住我" />
注意:name必须为remember-me
8、CSRF理解
什么是CSRF?
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
9、微服务权限案例
标签:springframework,SpringSecurity,学习,笔记,org,import,security,new,public 来源: https://www.cnblogs.com/zhouqiangshuo/p/16511969.html