快捷搜索:  as

我是这样使用SpringBoot(多认证方式)

目录

有的时刻,我们必要为用户供给多种认证要领。如:用户名密码登录、手术号验证码登录。下面实现Spring Security支持这两种登录要领。

增添Token

Spring Security默认应用UsernamePasswordAuthenticationToken包装登录哀求的信息,Token承袭之AbstractAuthenticationToken。Spring Security将信息封装成Token交给Provider处置惩罚。

这里增添一个MobileCodeAuthenticationToken类,承袭之AbstractAuthenticationToken。用于封装手机号验证码的哀求参数。后面会有响应的Provider处置惩罚这个Token。

创建包com.biboheart.demos.security.tokens,在包下创建类MobileCodeAuthenticationToken。可以查看UsernamePasswordAuthenticationToken类源码,参考它完成类的代码,内容如下:

package com.biboheart.demos.security.tokens;

import org.springframework.security.authentication.AbstractAuthenticationToken;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.SpringSecurityCoreVersion;

import java.util.Collection;

public class MobileCodeAuthenticationToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

private final Object principal;

private String credentials;

public MobileCodeAuthenticationToken(Object principal, String credentials) {

super(null);

this.principal = principal;

this.credentials = credentials;

setAuthenticated(false);

}

public MobileCodeAuthenticationToken(Object principal, Collection authorities) {

super(authorities);

this.principal = principal;

this.credentials = null;

super.setAuthenticated(true); // must use super, as we override

}

@Override

public String getCredentials() {

return this.credentials;

}

@Override

public Object getPrincipal() {

return this.principal;

}

public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {

if (isAuthenticated) {

throw new IllegalArgumentException(

"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");

}

super.setAuthenticated(false);

}

@Override

public void eraseCredentials() {

super.eraseCredentials();

credentials = null;

}

}

Provider

Provider实现AuthenticationProvider接口,它履行身份认证事情。前面用的是Spring Security默认的Provider进行认证,我们没有节制认证历程。在这里我们实现两个Provider,UsernamePasswordAuthenticationProvider用于调换系统默认的用户名密码认证营业,MobileCodeAuthenticationProvider用于履行手机号验证码认证营业。这两个类创建在包com.biboheart.demos.security.provider下。实现接口AuthenticationProvider,此中Authentication authenticate函数用于履行认证,supports函数用于筛选Token,假如在这里返回true,所有Token都邑认证。

package com.biboheart.demos.security.provider;

import com.biboheart.brick.utils.CheckUtils;

import org.springframework.security.authentication.AuthenticationProvider;

import org.springframework.security.authentication.BadCredentialsException;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

import org.springframework.security.core.Authentication;

import org.springframework.security.core.AuthenticationException;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.HashSet;

import java.util.Set;

public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {

@Override

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

String password = (String) authentication.getCredentials();

// 认证用户名

if (!"user".equals(username) && !"admin".equals(username)) {

throw new BadCredentialsException("用户不存在");

}

// 认证密码,暂时不加密

if ("user".equals(username) && !"123".equals(password) || "admin".equals(username) && !"admin".equals(password)) {

throw new BadCredentialsException("密码不精确");

}

UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(username,

authentication.getCredentials(), listUserGrantedAuthorities(username));

result.setDetails(authentication.getDetails());

return result;

}

@Override

public boolean supports(Class authentication) {

return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));

}

private Set listUserGrantedAuthorities(String username) {

Set authorities = new HashSet();

if (CheckUtils.isEmpty(username)) {

return authorities;

}

authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

if ("admin".equals(username)) {

authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

}

return authorities;

}

}

MobileCodeAuthenticationProvider

package com.biboheart.demos.security.provider;

import com.biboheart.brick.utils.CheckUtils;

import com.biboheart.demos.security.tokens.MobileCodeAuthenticationToken;

import org.springframework.security.authentication.AuthenticationProvider;

import org.springframework.security.authentication.BadCredentialsException;

import org.springframework.security.core.Authentication;

import org.springframework.security.core.AuthenticationException;

import org.springframework.security.core.GrantedAuthority;

import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.HashSet;

import java.util.Set;

public class MobileCodeAuthenticationProvider implements AuthenticationProvider {

@Override

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

String mobile = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

String code = (String) authentication.getCredentials();

if (CheckUtils.isEmpty(code)) {

throw new BadCredentialsException("验证码不能为空");

}

if (!"13999990000".equals(mobile)) {

throw new BadCredentialsException("用户不存在");

}

// 手机号验证码营业还没有开拓,先用4个0验证

if (!code.equals("0000")) {

throw new BadCredentialsException("验证码不精确");

}

MobileCodeAuthenticationToken result = new MobileCodeAuthenticationToken(mobile,

listUserGrantedAuthorities(mobile));

result.setDetails(authentication.getDetails());

return result;

}

@Override

public boolean supports(Class authentication) {

return (MobileCodeAuthenticationToken.class.isAssignableFrom(authentication));

}

private Set listUserGrantedAuthorities(String username) {

Set authorities = new HashSet();

if (CheckUtils.isEmpty(username)) {

return authorities;

}

authorities.add(new SimpleGrantedAuthority("ROLE_USER"));

return authorities;

}

}

完成Provider后,要将两个Provider加入设置设置设备摆设摆设中,使它们加入事情。改动SecurityConfiguration设置设置设备摆设摆设文件。首先实例化这两个Provider,然后将两Bean添加到configure(AuthenticationManagerBuilder auth)

package com.biboheart.demos.security;

import com.biboheart.demos.security.provider.MobileCodeAuthenticationProvider;

import com.biboheart.demos.security.provider.UsernamePasswordAuthenticationProvider;

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.EnableWebSecurity;

import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration

@EnableWebSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired

private BCryptPasswordEncoder passwordEncoder;

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

// 创建内存用户

/*auth.inMemoryAuthentication()

.withUser("user").password(passwordEncoder.encode("123")).roles("USER")

.and()

.withUser("admin").password(passwordEncoder.encode("admin")).roles("USER", "ADMIN");*/

auth

.authenticationProvider(usernamePasswordAuthenticationProvider())

.authenticationProvider(mobileCodeAuthenticationProvider());

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http

.authorizeRequests()

.antMatchers("/", "/home").permitAll() // 这三个目录不做安然节制

.anyRequest().authenticated()

.and()

.formLogin()

.loginPage("/login")// 自定义的登录页面

.permitAll()

.and()

.logout()

.logoutSuccessUrl("/");

}

@Bean

public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {

return new UsernamePasswordAuthenticationProvider();

}

@Bean

public MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {

return new MobileCodeAuthenticationProvider();

}

// spring security 必须有一个passwordEncoder

@Bean

public BCryptPasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

}

增添过滤器

在这个时刻,UsernamePasswordAuthenticationProvider已经起感化了。由于Spring Security用默认有一个UsernamePasswordAuthenticationFilter过滤器过滤login,在过滤器中会创建UsernamePasswordAuthenticationToken工具,UsernamePasswordAuthenticationProvider能够获得Token进行处置惩罚。虽然MobileCodeAuthenticationProvider已经在认证行列步队中,然则MobileCodeAuthenticationProvider是不会履行认证事情。MobileCodeAuthenticationToken是自定义的,没有地方天生它的实例,return (MobileCodeAuthenticationToken.class.isAssignableFrom(authentication));履行完成这名后就漂过了。

参考Spring Security UsernamePasswordAuthenticationToken的认证要领,我们也在UsernamePasswordAuthenticationFilter之前加一个过滤器,用户判断MobileCodeAuthenticationToken认证要领。可以用指定的参数,或者指定的URL,这里用的是URL判断,供给“/mobileCodeLogin”为手机号验证码登录的URL。这个Filter参考用户名密码的Filter实现,名称为MobileCodeAuthenticationFilter,从AbstractAuthenticationProcessingFilter承袭。接管两个参数分手为“mobile”和“code”。假如对照下,会发明与UsernamePasswordAuthenticationFilter异常像。代码如下:

package com.biboheart.demos.filter;

import com.biboheart.demos.security.tokens.MobileCodeAuthenticationToken;

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 javax.servlet.ServletException;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

public class MobileCodeAuthenticationFilter 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 MobileCodeAuthenticationFilter() {

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));

}

}

完成Filter实现后,必要将它加入到Filter序列中。加入措施是在SecurityConfiguration文件中,实例化Filter,然后在configure(HttpSecurity http)设置设置设备摆设摆设下加入http.addFilterBefore(mobileCodeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);即在UsernamePasswordAuthenticationFilter之前加入一个过滤器。记得将“/mobileCodeLogin”添加到容许通得中。改动后的SecurityConfiguration如下:

package com.biboheart.demos.security;

import com.biboheart.demos.filter.MobileCodeAuthenticationFilter;

import com.biboheart.demos.security.provider.MobileCodeAuthenticationProvider;

import com.biboheart.demos.security.provider.UsernamePasswordAuthenticationProvider;

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.crypto.bcrypt.BCryptPasswordEncoder;

import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration

@EnableWebSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Autowired

private BCryptPasswordEncoder passwordEncoder;

@Autowired

@Qualifier("authenticationManagerBean")

private AuthenticationManager authenticationManager;

@Override

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

// 创建内存用户

/*auth.inMemoryAuthentication()

.withUser("user").password(passwordEncoder.encode("123")).roles("USER")

.and()

.withUser("admin").password(passwordEncoder.encode("admin")).roles("USER", "ADMIN");*/

auth

.authenticationProvider(usernamePasswordAuthenticationProvider())

.authenticationProvider(mobileCodeAuthenticationProvider());

}

@Override

protected void configure(HttpSecurity http) throws Exception {

// @formatter:off

http

.authorizeRequests()

.antMatchers("/", "/home", "/mobileCodeLogin").permitAll() // 这三个目录不做安然节制

.anyRequest().authenticated()

.and()

.formLogin()

.loginPage("/login")// 自定义的登录页面

.permitAll()

.and()

.logout()

.logoutSuccessUrl("/");

http.addFilterBefore(mobileCodeAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

// @formatter:on

}

@Override

@Bean

public AuthenticationManager authenticationManagerBean() throws Exception {

return super.authenticationManagerBean();

}

@Bean

public MobileCodeAuthenticationFilter mobileCodeAuthenticationFilter() {

MobileCodeAuthenticationFilter filter = new MobileCodeAuthenticationFilter();

filter.setAuthenticationManager(authenticationManager);

return filter;

}

@Bean

public UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider() {

return new UsernamePasswordAuthenticationProvider();

}

@Bean

public MobileCodeAuthenticationProvider mobileCodeAuthenticationProvider() {

return new MobileCodeAuthenticationProvider();

}

// spring security 必须有一个passwordEncoder

@Bean

public BCryptPasswordEncoder passwordEncoder() {

return new BCryptPasswordEncoder();

}

}

改动界面

在登录界面中加入手机号验证码登录要领,试下效果。手机号和验证码在写在代码中的,分手是13999990000和0000。登录界面改动成:

密码登录:

用户名:

密码:

验证码登录:

手机号:

验证码:

完成开拓

假如必要更多的认证要领,同手机号验证码。步骤如下:

创建Token,承袭之AbstractAuthenticationToken

创建Provider,实现AuthenticationProvider

创建Filter,承袭之AbstractAuthenticationProcessingFilter

在设置设置设备摆设摆设类中实例化Filter和Provider

在Filter中处置惩罚哀求包装Token

Provider实例加入到auth.authenticationProvider

应用http.addFilterBefore在UsernamePasswordAuthenticationFilter之前加入Filter

启动办事,造访界面。应用流程与之前相同。差别是登录界面多了验证码登录表单,输入13999990000,验证码0000后也可以成功登录。

登录界面

此时,目录布局如下图。

目录布局

源码地址:https://gitee.com/biboheart/bh-springboot-demos.git

您可能还会对下面的文章感兴趣: