我们于2021/09/13 的学习目标是:SpringSecurity,核心任务为:
1、学习技术:
1)、SpringSecurity简介
2)、SpringSecurity快速入门
3)、UserDetailsService
4)、BCryptPasswordEncoder
5)、自定义登录
6)、认证过程其他常用配置
2、文档总结
1)、SpringSecurity简介
SpringSecurity是是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了 Spring IoC , DI(控制反转Inversion ofControl,DI:Dependency Injection 依赖注入) 和 AOP(面向切面编程) 功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
2)、SpringSecurity快速入门
pom.xml导入依赖
<!--Spring Security依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
使用浏览器访问http://localhost:8080/,自动跳转到默认登录页面
导入spring-boot-starter-security启动器后,Spring Security 已经生效,默认拦截全部请求,如果用户没有登录,跳转到登录页面。
默认的username为user,password打印在控制台中。注意每次运行生成的密码都不一致。
Using generated security password: e04ca9f5-f389-4982-b114-81d6488e0d59
3)、UserDetailsService
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。
package org.example.service.imp;
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 javax.annotation.Resource;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
if(!(s.equals("sdf")))
throw new UsernameNotFoundException("用户记录不存在!!");
String password = encoder.encode("345");
return new User(s,password, AuthorityUtils
.commaSeparatedStringToAuthorityList
("normal,ROLE_abc,/admin.html"));
}
}
4)、BCryptPasswordEncoder
BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,是对 bcrypt 强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认 10。
encode():给密码编码
@Test
public void encode() {
System.out.println(new BCryptPasswordEncoder().encode("myPwD!234567"));
//$2a$10$v5zc4C/OWNry0gb1rZanq.GCqv/2AS.kfzYc.aUfc8sB.vyCABKyW
//每次编码生成的字符都不相同
}
matches():验证输入的密码与进行编码后的原始密码是否相符
@Test
public void decode(){
System.out.println(new BCryptPasswordEncoder().matches("myPwD!234567","$2a$10$v5zc4C/OWNry0gb1rZanq.GCqv/2AS.kfzYc.aUfc8sB.vyCABKyW"));
//true
}
upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回true,否则返回 false。默认返回 false
@Test
public void encodeUpgrade() {
System.out.println(new BCryptPasswordEncoder().upgradeEncoding("$2a$10$v5zc4C/OWNry0gb1rZanq.GCqv/2AS.kfzYc.aUfc8sB.vyCABKyW"));
//false
}
5)、自定义登录
自定义login.html登录页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="userName" /><br/>
密码:<input type="password" name="userPwd" /><br/>
<input type="submit" value="登录" />
</form>
</body>
</html>
编写SecurityConfig配置类
package org.example.config;
import org.example.handler.LoginFailHandler;
import org.example.handler.LoginSuccessHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.annotation.Resource;
/**
* 自定义登录配置
*/
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private LoginSuccessHandler successHandler;
@Resource
private LoginFailHandler loginFailHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用csrf
http.csrf().disable();
//配置自定义登录表单页面信息
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successForwardUrl("/toMain")
.failureForwardUrl("/toError")
.usernameParameter("userName")
.passwordParameter("userPwd");
//资源放行
http.authorizeRequests()
.antMatchers("/login.html")
.permitAll()
.anyRequest()
.authenticated();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
public String passwordEncoder(String password) {
return new BCryptPasswordEncoder().encode(password);
}
}
自定义UserDetailServiceImp逻辑类
package org.example.service.imp;
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 javax.annotation.Resource;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private PasswordEncoder encoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
if(!(s.equals("admin")))
throw new UsernameNotFoundException("用户记录不存在!!");
String password = encoder.encode("123456");
return new User(s,password, AuthorityUtils
.commaSeparatedStringToAuthorityList
("normal,ROLE_abc,/admin.html"));
}
}
编写MainController控制器类
package org.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController {
@RequestMapping("toMain")
public String toMain(){
return "redirect:main.html";
}
public String toError(){
return "redirect:error.html";
}
}
6)、认证过程其他常用配置
编写页面error.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
操作失败,请重新登录 <a href= "/login.html">跳转</a>
</body>
</html>
修改控制器的方法
/**
* 失败后跳转页面
* @return
*/
@RequestMapping("/toError")
public String toError(){
return "redirect:/error.html";
}
配置器放行错误页面
//资源放行
http.authorizeRequests()
.antMatchers("/login.html","/error.html")//放行登录页面和错误页面
.permitAll()
.anyRequest()
.authenticated();
自定义登录成功处理器
使用successForwardUrl()时表示成功后转发请求到地址。内部是通过 successHandler() 方法进行控制成功后交给哪个类进行处理
//FormLoginConfigurer类文件
package org.springframework.security.config.annotation.web.configurers;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.web.authentication.ForwardAuthenticationFailureHandler;
import org.springframework.security.web.authentication.ForwardAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), (String)null);
this.usernameParameter("username");
this.passwordParameter("password");
}
public FormLoginConfigurer<H> loginPage(String loginPage) {
return (FormLoginConfigurer)super.loginPage(loginPage);
}
public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
((UsernamePasswordAuthenticationFilter)this.getAuthenticationFilter()).setUsernameParameter(usernameParameter);
return this;
}
public FormLoginConfigurer<H> passwordParameter(String passwordParameter) {
((UsernamePasswordAuthenticationFilter)this.getAuthenticationFilter()).setPasswordParameter(passwordParameter);
return this;
}
public FormLoginConfigurer<H> failureForwardUrl(String forwardUrl) {
this.failureHandler(new ForwardAuthenticationFailureHandler(forwardUrl));
return this;
}
public FormLoginConfigurer<H> successForwardUrl(String forwardUrl) {
this.successHandler(new ForwardAuthenticationSuccessHandler(forwardUrl));
return this;
}
public void init(H http) throws Exception {
super.init(http);
this.initDefaultLoginFilter(http);
}
protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingUrl) {
return new AntPathRequestMatcher(loginProcessingUrl, "POST");
}
private String getUsernameParameter() {
return ((UsernamePasswordAuthenticationFilter)this.getAuthenticationFilter()).getUsernameParameter();
}
private String getPasswordParameter() {
return ((UsernamePasswordAuthenticationFilter)this.getAuthenticationFilter()).getPasswordParameter();
}
private void initDefaultLoginFilter(H http) {
DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = (DefaultLoginPageGeneratingFilter)http.getSharedObject(DefaultLoginPageGeneratingFilter.class);
if (loginPageGeneratingFilter != null && !this.isCustomLoginPage()) {
loginPageGeneratingFilter.setFormLoginEnabled(true);
loginPageGeneratingFilter.setUsernameParameter(this.getUsernameParameter());
loginPageGeneratingFilter.setPasswordParameter(this.getPasswordParameter());
loginPageGeneratingFilter.setLoginPageUrl(this.getLoginPage());
loginPageGeneratingFilter.setFailureUrl(this.getFailureUrl());
loginPageGeneratingFilter.setAuthenticationUrl(this.getLoginProcessingUrl());
}
}
}