spring security(SpringBoot)自定义Provider实现多种认证方式
转载请注明出处spring boot 实例之 多登录方式–碧波之心简书
接上一篇:spring boot 实例之 用户登录。经过对spring security再次分析,觉得上一篇的实现方式有些不合理。不应该把UsernamePasswordAuthenticationFilter给替换了。今天我们来优化一下。让登录的扩展更合理一些。
调整目录结构
原目录结构:
删除SecurityController.java文件,换成用mvc config的方式来跳转到登录页面。
package com.biboheart.demo.user.security.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class SecurityMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/login").setViewName("login");
}
}
删除BhAuthenticationFilter.java。
创建过滤器
在filter中增加MobileCodeAuthenticationProcessingFilter。这里过滤的是”/mobileCodeLogin”的请求,用不同的请求地址来区分不同的登录认证方式。
package com.biboheart.demo.user.security.filter;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import com.biboheart.demo.user.security.tokens.MobileCodeAuthenticationToken;
public class MobileCodeAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_MOBILE_KEY = "mobile";
public static final String SPRING_SECURITY_FORM_CODE_KEY = "code";
private String mobileParameter = SPRING_SECURITY_FORM_MOBILE_KEY;
private String codeParameter = SPRING_SECURITY_FORM_CODE_KEY;
private boolean postOnly = true;
public MobileCodeAuthenticationProcessingFilter() {
super(new AntPathRequestMatcher("/mobileCodeLogin", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
String code = obtainCode(request);
if (mobile == null) {
mobile = "";
}
if (code == null) {
code = "";
}
mobile = mobile.trim();
code = code.trim();
AbstractAuthenticationToken authRequest = new MobileCodeAuthenticationToken(mobile, code);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
protected String obtainCode(HttpServletRequest request) {
return request.getParameter(codeParameter);
}
protected void setDetails(HttpServletRequest request,
AbstractAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
修改SecurityConfiguration
主要修改过滤器的插入位置
package com.biboheart.demo.user.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.biboheart.demo.user.security.filter.MobileCodeAuthenticationProcessingFilter;
import com.biboheart.demo.user.security.provider.MobileCodeAuthenticationProvider;
import com.biboheart.demo.user.security.provider.UsernamePasswordAuthenticationProvider;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(mobileCodeAuthenticationProvider())
.authenticationProvider(usernamePasswordAuthenticationProvider());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
http.addFilterBefore(mobileCodeAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
// @formatter:on
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public MobileCodeAuthenticationProcessingFilter mobileCodeAuthenticationProcessingFilter() {
MobileCodeAuthenticationProcessingFilter filter = new MobileCodeAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
@Bean
public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {
return new UsernamePasswordAuthenticationProvider();
}
@Bean
public MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {
return new MobileCodeAuthenticationProvider();
}
}
修改页面
index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>
点击 <a th:href="@{/hello}">这里</a> 查看信息.
</p>
</body>
</html>
hello.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Hello World!</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
<a href="/logout">注销</a>
<form th:action="@{/logout}" method="post">
<input type="submit" value="登出" />
</form>
</body>
</html>
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div th:if="${param.error}">用户名或密码错误</div>
<div th:if="${param.logout}">成功退出登录</div>
<form th:action="@{/login}" method="post">
<div>
<label> 用户名: <input type="text" name="username" />
</label>
</div>
<div>
<label> 密码: <input type="password" name="password" />
</label>
</div>
<div>
<input type="submit" value="登录" />
</div>
</form>
<span>-------------用手机号验证码登录-------------</span>
<form th:action="@{/mobileCodeLogin}" method="post">
<div>
<label> 手机号: <input type="text" name="mobile" />
</label>
</div>
<div>
<label> 验证码: <input type="text" name="code" />
</label>
</div>
<div>
<input type="submit" value="登录" />
</div>
</form>
</body>
</html>
测试
分别用手机号+验证码、用户名+密码登录。这样看起来更有条理些。
总结
当前目录结构
修改后的多认证方式,更方便扩展。假如要增加一个邮箱+验证码。分成以下几步:
1. 增加一个token (EmailCodeAuthenticationToken)
2. 增加一个provider (EmailCodeAuthenticationProvider)
3. 增加一个filter (EmailCodeAuthenticationProcessingFilter)
4. 在SecurityConfiguration中把三个项加入相应的位置
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(mobileCodeAuthenticationProvider())
.authenticationProvider(emailCodeAuthenticationProvider())
.authenticationProvider(usernamePasswordAuthenticationProvider());
}
...
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
...
http.addFilterBefore(mobileCodeAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(emailCodeAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
// @formatter:on
}
...
@Bean
public EmailCodeAuthenticationProcessingFilter emailCodeAuthenticationProcessingFilter() {
EmailCodeAuthenticationProcessingFilter filter = new EmailCodeAuthenticationProcessingFilter();
filter.setAuthenticationManager(authenticationManager);
return filter;
}
...
@Bean
public EmailCodeAuthenticationProvider emailCodeAuthenticationProvider() {
return new EmailCodeAuthenticationProvider();
}
用户名+密码的认证就保留了原有的业务。每一个认证的增加是各自独立的。后续要增加的Oauth2认证还要用到usernamePasswordAuthenticationProvider的认证,不至于出现意想不到的问题。
推荐阅读
-
spring security(SpringBoot)自定义Provider实现多种认证方式
-
spring security 5.x实现兼容多种密码的加密方式
-
spring security 5.x实现兼容多种密码的加密方式
-
SpringBoot集成Spring security JWT实现接口权限认证
-
Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码
-
Spring Security 自定义短信登录认证的实现
-
SpringBoot集成Spring security JWT实现接口权限认证
-
Spring Security 自定义短信登录认证的实现
-
Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码