目录
基于尚硅谷web学习
Security配置:
两个核心接口UserDetailsService与PasswordEncoder:
2.PasswordEncoder 给密码加密
Web项目权限控制方案
?(11条消息) SpringSecurity回顾_Fairy要carry的博客-CSDN博客
configure(AuthenticationManagerBuilder auth):
configure(HttpSecurity http)
注解(对于处理请求方法的)
用户注销:
记住我功能
CSRF
UsernamePasswordAuthenticationFilter:
功能:判断请求地址还有请求方式,如果不是/j_spring_security_check,就到下一个过滤器
并且还能验证用户密码信息并且返回Authentication类;
如果验证成功,SessionAuthenticationStrategy中的方法onAuthentication()判断用户能否重复登陆,是否二次登陆——根据你的配置文件决定,如果用户登陆满足条件则再执行successfulAuthentication(配置中的验证成功链接),如果失败则还是执行unsuccessfulAuthentication方法
具体:
(11条消息) UsernamePasswordAuthenticationFilter是登陆用户密码验证过滤器,_朱智文的专栏-CSDN博客
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
username = username != null ? username : "";
username = username.trim();
String password = this.obtainPassword(request);
password = password != null ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
Security配置:
两个核心接口UserDetailsService与PasswordEncoder:
UserDetailService:
?查询数据库中的用户名和密码:
实现UserDetailService接口,并且重写loadUserByUsername方法返回User对象
package com.wuyuhang.security.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.wuyuhang.security.Utils.ToMap;
import com.wuyuhang.security.mapper.UserMapper;
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.stereotype.Service;
import java.util.List;
/**
* @author diao 2022/2/23
*/
//自定义一个名为userDetailsService的组件注入到容器中
@Service("userDetailsService")
public class MyUserDetailService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.selectOne(ToMap.getInstance().addUser(username).getMap());
//判断
if (user == null) {
throw new UsernameNotFoundException("用户名不存在!");
}
/*当数据库查询用户信息不为null,给user设置权限,并且返回用户信息
* */
//授权Role_sale是角色
List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList("admins,ROLE_sale");
// 返回用户信息,返回后我们就可以正常访问controller了
return new User(user.getUsername(), new BCryptPasswordEncoder().encode(user.getPassword()), authorityList);
}
}
2.PasswordEncoder 给密码加密
用于User返回对象里面的密码加密
public interface PasswordEncoder {
/**
* Encode the raw password. Generally, a good encoding algorithm applies a SHA-1 or
* greater hash combined with an 8-byte or greater randomly generated salt.
*/
String encode(CharSequence rawPassword);
/**
* Verify the encoded password obtained from storage matches the submitted raw
* password after it too is encoded. Returns true if the passwords match, false if
* they do not. The stored password itself is never decoded.
*
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
*/
boolean matches(CharSequence rawPassword, String encodedPassword);
/**
* Returns true if the encoded password should be encoded again for better security,
* else false. The default implementation always returns false.
* @param encodedPassword the encoded password to check
* @return true if the encoded password should be encoded again for better security,
* else false.
*/
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
Web项目权限控制方案
三种方式:
1.通过配置文件根据webSecurityAutoConfiguration类中配置文件可以知道;
2.在配置类(继承WebSecurityConfigurer)重写configure方法,将用户名密码权限啥的放入认证空间中;
3.通过实现UserDetailService接口,将用户信息进行匹配
在WebSecurityConfigurerAdapter中我们可以完成所有Security配置,WebSecurityConfigurerAdapter会产生拦截器链;
package com.wuyuhang.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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
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;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/**
* @author diao 2022/2/23
*/
//继承WebSecurityAdapter可以得到Security默认的安全功能
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
//注入数据源
@Autowired
private DataSource dataSource;
//配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
//configure方法可以配置用户名和密码,并且利用BCryptPasswordEncoder进行密码加密
@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 {
//退出,请求logout,成功后转到请求方法处理test/hello
http.logout().logoutUrl("/logout")
.logoutSuccessUrl("/test/hello").permitAll();
//配置没有权限访问403——>跳转到自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin()//自定义自己编写的前端页面
.loginPage("/login.html")//登录页面设置
.loginProcessingUrl("/user/login")//页面登录表单登录访问路径
.defaultSuccessUrl("/success.html").permitAll()//登录成功后跳转路径
.failureUrl("/login?error")
.and()
.authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
// .antMatchers("/test/index").hasAnyAuthority("admins,manager") //只有具有admins权限才能访问这个路径
.antMatchers("/test/index").hasRole("sale") //根据角色访问
.anyRequest().authenticated()
.and()
.rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService);
// .and()
// .csrf().disable();//关闭csrf防护
}
}
认证管理器的建造器,如果要使用该功能需要配置一个userDetailsService和PasswordEncoder;
为什么要配置UserDetailService,他的作用是什么?
因为我们需要被认证的用户作为管理,那么UserDetailService作用就是:在认证器中根据传过来的用户名进行查找,PasswordEncoder就是将密码进行比对;
认证成功后返回一个Authentication对象;
认证器:
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return (DaoAuthenticationConfigurer)this.apply(new DaoAuthenticationConfigurer(userDetailsService));
}
与上述的区别是什么?
configure(HttpSecurity http)作用是:能够配置security的认证和授权——>对于各种请求进行设置权限;还可以配置csrf,记住我,和登录登出的一些配置,相当于功能的实现地;而上面是看这个用户没有有资格得到security的功能效果,相当于是一个入口;
@Override
protected void configure(HttpSecurity http) throws Exception {
//退出,请求logout,成功后转到请求方法处理test/hello
http.logout().logoutUrl("/logout")
.logoutSuccessUrl("/test/hello").permitAll();
//配置没有权限访问403——>跳转到自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin()//自定义自己编写的前端页面
.loginPage("/login.html")//登录页面设置
.loginProcessingUrl("/user/login")//页面登录表单登录访问路径
.defaultSuccessUrl("/success.html").permitAll()//登录成功后跳转路径
.failureUrl("/login?error")
.and()
.authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
// .antMatchers("/test/index").hasAnyAuthority("admins,manager") //只有具有admins权限才能访问这个路径
.antMatchers("/test/index").hasRole("sale") //根据角色访问
.anyRequest().authenticated()
.and()
.rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService);
注解(对于处理请求方法的)
1.@Secured:
判断是否有该角色,角色需要在前面加前缀"ROLE_";
注意:
需要在启动类或者其他的配置类中+@EnableGlobalMethodSecurity(securedEnabled = true)
2.@PreAuthorize
进入方法前进行权限 验证或角色 验证
启动类上加@EnableGlobalMethodSecurity(securedEnabled = true)
3.@PostAuthorize
适合在方法执行完之后进行校验,适合验证有返回值的权限。
4.@PostFilter
权限验证后对返回数据进行过滤
代码:
package com.wuyuhang.security.controller;
import com.wuyuhang.security.pojo.User;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* @author diao 2022/2/23
*/
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/hello")
public String hello(){
return "hello";
}
@GetMapping("/index")
public String index(){
return "index";
}
//专门作为角色的方法
@GetMapping("/update")
//设置能够访问的角色
@Secured({"ROLE_sale","ROLE_manager"})
public String update(){
return "update ok";
}
@GetMapping("/delete")
//当你的用户中有admin这个权限就能访问这个方法
@PreAuthorize("hasAuthority('admins')")
public String delete(){
return "delete Ok";
}
/*还有一种情况:
先进行方法处理请求,得到结果
security验证其结果,也就是说对方法执行完后进行一个校验
* */
@GetMapping("/add")
@PostAuthorize("hasAuthority('teacher')")
public String add(){
//方法体中的这个是可以输出的
System.out.println("开始add...");
//因为没有该权限,所以无法访问一下内容
return "hello teacher,add Ok";
}
@GetMapping("/getAll")
@PostAuthorize("hasAnyAuthorize('admins')")
//PostFilter("filterObject.xxx=='xxx'")会将集合类型中不满足条件的元素进行移除
@PostFilter("filterObject.username=='admin1'")
public List<User> getAllUser(){
ArrayList<User> list = new ArrayList<>();
list.add(new User(11,"admin1","6666"));
list.add(new User(12,"admin2","888"));
System.out.println(list);
return list;
}
}
用户注销:
在继承WebSecurityConfigurerAdapter类(类似boot中的WebMvcConfigurerAdapter)中,重新configure方法,里面配置即可;
//退出,请求logout,成功后转到请求方法处理test/hello
http.logout().logoutUrl("/logout")
.logoutSuccessUrl("/test/hello").permitAll();
页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功!
<a href="/logout">退出</a>
</body>
</html>
记住我功能
(11条消息) SpringSecurity--记住我功能原理_喝醉的咕咕鸟-CSDN博客_springsecurity记住我原理
Cookie:客户端机制,内容存在浏览器中;
SpringSecurity安全框架机制自动登录:
原理
当第二次访问时,获取cookie中的信息和token,和数据库中的信息进行对比,认证成功就是可以登录;
?案例:
1.配置类,注入数据源,操作数据库对象;
package com.wuyuhang.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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
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;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
/**
* @author diao 2022/2/23
*/
//继承WebSecurityAdapter可以得到Security默认的安全功能
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Autowired
private UserDetailsService userDetailsService;
//注入数据源
@Autowired
private DataSource dataSource;
//配置对象
@Bean
public PersistentTokenRepository persistentTokenRepository(){
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
return jdbcTokenRepository;
}
//configure方法可以配置用户名和密码,并且利用BCryptPasswordEncoder进行密码加密
@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 {
//退出,请求logout,成功后转到请求方法处理test/hello
http.logout().logoutUrl("/logout")
.logoutSuccessUrl("/test/hello").permitAll();
//配置没有权限访问403——>跳转到自定义页面
http.exceptionHandling().accessDeniedPage("/unauth.html");
http.formLogin()//自定义自己编写的前端页面
.loginPage("/login.html")//登录页面设置
.loginProcessingUrl("/user/login")//页面登录表单登录访问路径
.defaultSuccessUrl("/success.html").permitAll()//登录成功后跳转路径
.failureUrl("/login?error")
.and()
.authorizeRequests()
.antMatchers("/","/test/hello","/user/login").permitAll()//设置哪些路径可以直接访问,不需要认证
// .antMatchers("/test/index").hasAnyAuthority("admins,manager") //只有具有admins权限才能访问这个路径
.antMatchers("/test/index").hasRole("sale") //根据角色访问
.anyRequest().authenticated()
.and()
.rememberMe().tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(60)
.userDetailsService(userDetailsService);
// .and()
// .csrf().disable();//关闭csrf防护
// http.formLogin()
// .loginPage("/login.html")
// .loginProcessingUrl("/user/login")
// .defaultSuccessUrl("/success.html").permitAll()
// .failureUrl("/login?error")
// .and()
// .authorizeRequests()
// .antMatchers("/test/index").hasRole("sale")
// .antMatchers("/test/index").hasAnyAuthority("admins,manager")
// .and()
// .rememberMe().tokenRepository(persistentTokenRepository())
//
}
}
3.在前端页面添加复选框(记住我)
注意:input框中的name属性一定要是remeber-me
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--post提交受到了security的保护-->
<form action="/user/login" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
用户名:<input type="text" name="username">
密码:<input type="text" name="password">
<input type="checkbox" name="remember-me" title="记住我">
<input type="submit" value="登录">
</form>
</body>
</html>
CSRF
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
从Spring Security4.0之后默认开启,它会针对PATCH,POST,PUT,DELETE方法进行保护
实现原理 认证成功后生成csrfToken保存到HttpSession或者Cookie中。每次请求都会带着这个Token 值,利用这个Token值和session中的token值做比较,一样则允许访问;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
//生成token存入session
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
//
request.setAttribute(CsrfToken.class.getName(), csrfToken);
//拿到表单传过来的token值
request.setAttribute(csrfToken.getParameterName(), csrfToken);
//判断和session中的token中是否一样
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
filterChain.doFilter(request, response);
}
实现案例:
在表单中增加隐藏项
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--post提交受到了security的保护-->
<form action="/user/login" method="post">
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
用户名:<input type="text" name="username">
密码:<input type="text" name="password">
<input type="checkbox" name="remember-me" title="记住我">
<input type="submit" value="登录">
</form>
</body>
</html>
|