我们于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;@Servicepublic class UserDetailsServiceImpl implements UserDetailsService {@Resourceprivate PasswordEncoder encoder;@Overridepublic 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;/*** 自定义登录配置*/@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Resourceprivate LoginSuccessHandler successHandler;@Resourceprivate LoginFailHandler loginFailHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {//禁用csrfhttp.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();}@Beanpublic 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;@Servicepublic class UserDetailsServiceImpl implements UserDetailsService {@Resourceprivate PasswordEncoder encoder;@Overridepublic 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;@Controllerpublic 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());}}
}