![](https://img.haomeiwen.com/i8574472/8942a47ed447154e.png)
![](https://img.haomeiwen.com/i8574472/53055775f3c8e944.png)
![](https://img.haomeiwen.com/i8574472/0a94f856204eed11.png)
![](https://img.haomeiwen.com/i8574472/b9f6ad9a68f62862.png)
一.自定义用户认证逻辑
![](https://img.haomeiwen.com/i8574472/ef803b646ca24a42.png)
编写适配器
com.imooc.security.browser.BrowserSecurityConfig
package com.imooc.security.browser;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
/**
* @Author:LovingLiu
* @Description: Security安全配置的适配器
* @Date:Created in 2019-12-18
*/
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(). // 声明验证方式为表单登录
and(). //
authorizeRequests(). // 对请求进行授权
anyRequest(). // 任意请求
authenticated(); //都需要身份认证
}
}
1.处理用户信息获取逻辑
MyUserDetailService
package com.imooc.security.browser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Component;
/**
* @Author:LovingLiu
* @Description: 实现接口 UserDetails
* @Date:Created in 2019-12-18
*/
@Component
public class MyUserDetailService implements UserDetailsService {
private Logger log = LoggerFactory.getLogger(getClass());
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("登录用户名:"+username);
/**
* 此处读取数据库和权限
* 这里暂时先写死了
*/
return new User(username, "{noop}12345", AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
为什么要添加{noop}
在Spring Security 5.0之前,默认的PasswordEncoder
是NoOpPasswordEncoder
,它需要纯文本密码。
在Spring Security 5
中,默认值为DelegatingPasswordEncoder
,它需要密码存储格式。
2.处理用户校验逻辑
package com.imooc.security.browser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.Component;
/**
* @Author:LovingLiu
* @Description: 实现接口 UserDetails
* @Date:Created in 2019-12-18
*/
@Component
public class MyUserDetailService implements UserDetailsService {
private Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("登录用户名:"+username);
/**
* 1.此处读取数据库和权限
* 这里暂时先写死了
*/
/**
* 2.查看账户状态
*/
boolean isEnabled = isEnabled(null);
boolean isAccountNonExpired = isAccountNonExpired(null);
boolean isAccountNonLocked = isAccountNonLocked(null);
boolean isCredentialsNonExpired = isCredentialsNonExpired(null);
return new User(username,
passwordEncoder.encode("12345"),
isEnabled,
isAccountNonExpired,
isCredentialsNonExpired,
isAccountNonLocked,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
/**
* 是否可用(自定义实现)
* @param arg
* @return
*/
public boolean isEnabled(Object arg) {
return true;
}
/**
* 账号是否过期
* @param arg
* @return
*/
public boolean isAccountNonExpired(Object arg) {
return true;
}
/**
* 账号是否被冻结
* @param arg
* @return
*/
public boolean isAccountNonLocked(Object arg) {
return true;
}
/**
* 账号密码是否过期(因为存在密码的有效期为30天的那种高级防护系统)
* @param arg
* @return
*/
public boolean isCredentialsNonExpired(Object arg) {
return true;
}
}
![](https://img.haomeiwen.com/i8574472/8f55cfffbfb8de12.png)
3.用户密码的加密解密
com.imooc.security.browser.util.Md5PasswordEncoder
注意:一定要实现接口PasswordEncoder
,当使用表单登录
的时候会自动调用
package com.imooc.security.browser.util;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/**
* @Author:LovingLiu
* @Description: 自定义MD5加解密处理
* @Date:Created in 2019-12-18
*/
@Component
public class Md5PasswordEncoder implements PasswordEncoder {
/**
* @param rawPassword 要编码的原始密码
* @return
*/
@Override
public String encode(CharSequence rawPassword) {
return "md5"+rawPassword;
}
/**
*
* @param rawPassword 原始密码(未做任何编码处理)
* @param encodedPassword 编码之后的密码
* @return
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return ("md5"+rawPassword).equals(encodedPassword);
}
}
UserDetail和User之间的关系
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("登录用户名:"+username);
/**
* 此处读取数据库和权限
* 这里暂时先写死了
*/
/**
* 查看账户状态
*/
boolean isEnabled = isEnabled(null);
boolean isAccountNonExpired = isAccountNonExpired(null);
boolean isAccountNonLocked = isAccountNonLocked(null);
boolean isCredentialsNonExpired = isCredentialsNonExpired(null);
return new User(username,
passwordEncoder.encode("12345"),
isEnabled,
isAccountNonExpired,
isCredentialsNonExpired,
isAccountNonLocked,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
UserDetail 是一个接口,验证用户必须要实现该接口
1.org.springframework.security.core.userdetails.UserDetails
public interface UserDetails extends Serializable {
...
}
而User是security默认实现
2.org.springframework.security.core.userdetails.User
public class User implements UserDetails, CredentialsContainer {
...
}
二.个性化用户认证流程
![](https://img.haomeiwen.com/i8574472/8f6f9a9a292be1a3.png)
1.自定义登录界面
com.imooc.security.browser.BrowserSecurityConfig
package com.imooc.security.browser;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author:LovingLiu
* @Description: Security安全配置的适配器
* @Date:Created in 2019-12-18
*/
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(). // 声明验证方式为表单登录
loginPage("/imooc-signIn.html"). // 自定义登录界面
loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
and(). //
authorizeRequests(). // 对请求进行授权
antMatchers("/imooc-signIn.html","/authentication/form").permitAll().// 配置不进行认证请求
anyRequest(). // 任意请求
authenticated(). // 都需要身份认证
and().
csrf().disable(); // 关闭跨站请求防护
}
}
src/main/resources/resources/imooc-signIn.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="password"></td>
</tr>
<tr>
<td colspan="2"><button type="submit">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
自定义登录页面和实现登录逻辑
![](https://img.haomeiwen.com/i8574472/705973fcc31437a5.png)
修改loginPage
com.imooc.security.browser.BrowserSecurityConfig
package com.imooc.security.browser;
import com.imooc.security.core.properties.SecurityProperties;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @Author:LovingLiu
* @Description: Security安全配置的适配器
* @Date:Created in 2019-12-18
*/
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(). // 声明验证方式为表单登录
loginPage("/authentication/require"). // 自定义登录界面
loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
and(). //
authorizeRequests(). // 对请求进行授权
antMatchers("/authentication/require","/authentication/form",securityProperties.getBrowser().getLoginPage()).permitAll().// 配置不进行认证请求
anyRequest(). // 任意请求
authenticated(). // 都需要身份认证
and().
csrf().disable(); // 关闭跨站请求防护
}
}
创建对应的Controller
com.imooc.security.browser.BrowserSecurityController
package com.imooc.security.browser;
import com.imooc.security.browser.support.SimpleResponse;
import com.imooc.security.core.properties.SecurityProperties;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @Author:LovingLiu
* @Description: Security安全配置的适配器
* @Date:Created in 2019-12-18
*/
@RestController
public class BrowserSecurityController{
private Logger log= LoggerFactory.getLogger(BrowserSecurityController.class);
private RequestCache requestCache = new HttpSessionRequestCache();// 会将http请求缓存起来
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperties securityProperties;
/**
* 当需要身份认证时 跳转到这里
* @param request
* @param response
* @return
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response){
SavedRequest savedRequest = requestCache.getRequest(request,response);
if(savedRequest != null){
String targetUrl = savedRequest.getRedirectUrl();
log.info("【引发跳转的请求的是:{}】",targetUrl);
// 引发跳转的请求是已Html结尾的,直接跳转到登录页面
if(StringUtils.endsWithIgnoreCase(targetUrl,".html")){
try{
redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());// 跳转用户自定义页面
}catch (Exception e){
e.printStackTrace();
}
}
}
// 非Html请求 返会401状态码 和 相应的信息
return new SimpleResponse(401,"访问的服务需要身份信息,请引导用户到登录页面");
}
}
修改application.yml
/Users/lovingliu/Desktop/project/imooc-security/imooc-security-demo/src/main/resources/application.yml
...
# 配置自定义登录页面
imooc:
security:
browser:
login-page: /demo-signIn.html
...
增加自定义登录页面
imooc-signIn.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>自定义登录页面</title>
</head>
<body>
这是自定义登录页面
</body>
</html>
增加配置类
com.imooc.security.core.properties.BrowserProperties
package com.imooc.security.core.properties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
public class BrowserProperties {
private String loginPage = "/imooc-signIn.html"; // 指定默认跳转页面
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}
com.imooc.security.core.properties.SecurityProperties
package com.imooc.security.core.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
private BrowserProperties browser = new BrowserProperties();
public BrowserProperties getBrowser() {
return browser;
}
public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}
com.imooc.security.core.SecurityCoreConfig
package com.imooc.security.core;
import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}
![](https://img.haomeiwen.com/i8574472/18f3eef327f42b63.png)
2.自定义登录成功处理
在上面的处理逻辑中(跳转登录页面),并不完全适用于所有的项目(如:SPA),有可能需要我们返回json格式的信息
在配置文件中增加登录方式的配置
新增文件com.imooc.security.core.properties.LoginType
package com.imooc.security.core.properties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
public enum LoginType {
REDIRECT,
JSON
}
在com.imooc.security.core.properties.BrowserProperties
中添加配置loginType
package com.imooc.security.core.properties;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
public class BrowserProperties {
private String loginPage = "/imooc-signIn.html"; // 指定默认跳转页面
private LoginType loginType = LoginType.JSON; // 指定登录方式
public String getLoginPage() {
return loginPage;
}
public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
public LoginType getLoginType() {
return loginType;
}
public void setLoginType(LoginType loginType) {
this.loginType = loginType;
}
}
新增文件com.imooc.security.browser.authentication.ImoocAuthenticationSuccessHandler
package com.imooc.security.browser.authentication;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginType;
import com.imooc.security.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author:LovingLiu
* @Description: 自定义登录成功处理器行为
* @Date:Created in 2019-12-22
*/
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger log = LoggerFactory.getLogger(getClass());
/**
* springMVC 自动注册:ObjectMapper
*/
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
// authentication 封装的认证信息
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("【登录成功】");
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}else {
// 调用父类的处理方法
super.onAuthenticationSuccess(request,response,authentication);
}
}
}
新增文件com.imooc.security.browser.authentication.ImoocAuthenticationFailureHandler
package com.imooc.security.browser.authentication;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginType;
import com.imooc.security.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
@Component("imoocAuthenticationFailureHandler")
public class ImoocAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("【登录失败】");
if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception));
}else{
super.onAuthenticationFailure(request,response,exception);
}
}
}
修改配置文件com.imooc.security.browser.BrowserSecurityConfig
package com.imooc.security.browser;
import com.imooc.security.core.properties.SecurityProperties;
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.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* @Author:LovingLiu
* @Description: Security安全配置的适配器
* @Date:Created in 2019-12-18
*/
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin(). // 声明验证方式为表单登录
loginPage("/authentication/require"). // 自定义登录界面
loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
successHandler(imoocAuthenticationSuccessHandler). // 使用自定义成功处理器
failureHandler(imoocAuthenticationFailureHandler). // 使用自定义失败处理器
and(). //
authorizeRequests(). // 对请求进行授权
antMatchers("/authentication/require","/authentication/form",securityProperties.getBrowser().getLoginPage()).permitAll().// 配置不进行认证请求
anyRequest(). // 任意请求
authenticated(). // 都需要身份认证
and().
csrf().disable(); // 关闭跨站请求防护
}
}
![](https://img.haomeiwen.com/i8574472/a4402bffa579c359.png)
3.自定义登录失败处理
新增文件com.imooc.security.browser.authentication.ImoocAuthenticationFailureHandler
package com.imooc.security.browser.authentication;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginType;
import com.imooc.security.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @Author:LovingLiu
* @Description:
* @Date:Created in 2019-12-22
*/
@Component("imoocAuthenticationFailureHandler")
public class ImoocAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private Logger log = LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("【登录失败】");
if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception));
}else{
super.onAuthenticationFailure(request,response,exception);
}
}
}
![](https://img.haomeiwen.com/i8574472/155d289ed33294a9.png)
![](https://img.haomeiwen.com/i8574472/ad45bc22f0dcbfbe.png)
总结
到目前为止我们只提供了两个Filter
BrowserSecurityController
实现自定义登录界面,其内部也实现了请求拦截,严格来说它并不是一个Filter
![](https://img.haomeiwen.com/i8574472/981094894219e8d6.png)
同时提供了自定义登录请求结果的
Filter
![](https://img.haomeiwen.com/i8574472/e10023ea08555d71.png)
顺序是先执行错误的
自定义登录的Filter
-> 登录请求结果Filter
网友评论