SpringSecurityOauth2关于⾃定义登录的⼏种解决⽅案(⼆)
Spring Security Oauth2关于⾃定义登录的⼏种解决⽅案(⼆)
上⼀篇⽂章,通过简单的⽅式进⾏⾃定义⽤户登录授权的动作,投机取巧使⽤了他的内部循环处理机制,完成了我们所需要的功能,本篇将介绍⼀下⾃定义模式的登录⽅式(例如:原始oauth2⾃带的密码模式,授权码模式,简单模式,客户端模式)。最终其实我们还是采⽤了密码模式的思路,只是修改了两个参数⽽已。
本篇采⽤新增TokenGranter完成⾃定义登录
第⼀步:创建⼀个新的Token,继承AbstractAuthenticationToken
其中,Token的关键在于Authenticated是否授权,需要两个构造,⼀个为未鉴权Token,以及⼀个已鉴权Token,后续也需要判断是否为该Token,才进⾏相关判断
ample.customoauth.authentication.account;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.GrantedAuthority;
import java.util.Collection;
/**
我个⼈将验证码以及密码模式封装在⼀起,所以传⼊三个参数,通过判断判断短信验证码是否为空来决定采⽤哪种验证⽅式,是密码验证还是短信验证码验证,**/
public class AccountLoginToken extends AbstractAuthenticationToken {
//⽤户名
private final Object principal;
//密码
private Object credentials;
//短信验证码
private String vcode;
/**
* 构建⼀个没有鉴权的 AccountLoginToken,⼿机号,密码,验证码
*/
public AccountLoginToken(Object principal, Object credentials,String vcode){
super(null);
this.principal = principal;
this.vcode = vcode;
setAuthenticated(false);
}
/**
* 构建拥有鉴权的 AccountLoginToken
*/
public AccountLoginToken(Object principal, Collection<?extends GrantedAuthority> authorities){
super(authorities);
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
}
@Override
public Object getCredentials(){
dentials;
}
@Override
public Object getPrincipal(){
return this.principal;
}
public String getVcode(){
return this.vcode;
}
}
第⼆步:创建AccountAuthenticationProvider,实现AuthenticationProvider
此功能在第⼀篇已经介绍,不过多赘述,直接贴⼊代码
ample.customoauth.authentication.account;
import com.alibaba.fastjson.JSON;
import com.yfhcloudmon.yfhutils.Constant;
import com.yfhcloudmon.yfhutils.WebResponse;
import com.ums.ResultEnum;
slf4j.Slf4j;
import org.apachemons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.RedisTemplate;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.Authentication;
import org.AuthenticationException;
import org.userdetails.UserDetails;
import org.userdetails.UserDetailsService;
import org.pto.factory.PasswordEncoderFactories;
import org.pto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
publisher.Mono;
/**
* 后端短信认证校验
* 原理:AuthenticationProvider通常按照认证请求链顺序去执⾏,
* ⼀个返回⾮null响应表⽰程序验证通过,
* 不再尝试验证其它的provider;如果后续提供的⾝份验证程序成功地对请求进⾏⾝份认证,
* 则忽略先前的⾝份验证异常及null响应,并将使⽤成功的⾝份验证。
* 如果没有provider提供⼀个⾮null响应,或者有⼀个新的抛出AuthenticationException,
* 那么最后的AuthenticationException将会抛出。
*
* @author : Windy
* @version :1.0
* @since : 2020/12/22 15:51
*/
@Component
@Slf4j
public class AccountAuthenticationProvider implements AuthenticationProvider {
//这个代码我就不贴了,上⼀篇有,根据⾃⾝业务进⾏修改即可
@Autowired
@Qualifier("securityUserDetailsService")
private UserDetailsService securityUserDetailsService;
//由于使⽤@Autowired进⾏加载,似乎有冲突,暂时我默认直接在⽤的地⽅加载,第⼀篇那地⽅也需要修改以下,暂时不在config中⽤@bean进⾏初始化。private PasswordEncoder passwordEncoder= ateDelegatingPasswordEncoder();
//注⼊REDIS,存放短信验证码使⽤
@Autowired
private RedisTemplate redisTemplate;
//认证⽅法
@Override
public Authentication authenticate(Authentication authentication)throws AuthenticationException {
AccountLoginToken accountLoginToken =(AccountLoginToken) authentication;
System.out.println("===进⼊USER登录验证环节====="+ JSONString(accountLoginToken));
UserDetails userDetails = securityUserDetailsService.Name());
if(StringUtils.Vcode())){
//如果vcode不为空,则为短信验证码校验
if(redisTemplate.opsForValue().get(Constant.REDIS_SMS_LOGIN_CODE + Name())!= null){
String redisCode = redisTemplate.opsForValue().get(Constant.REDIS_SMS_LOGIN_CODE + Name()).toString();
if(!redisCode.Vcode())){
throw new BadCredentialsException("验证码不正确");
}
}else{
throw new BadCredentialsException("验证码已失效!");
}
//校验成功,返回⼀个授权过的Token
return new AccountLoginToken(userDetails, Authorities());
}else{
//其他模式直接采⽤⽤户密码判断
if(passwordEncoder.Credentials().toString(), Password())){
//返回⼀个授权过的Token
return new AccountLoginToken(userDetails, Authorities());
}
}
throw new BadCredentialsException("⽤户名密码不正确");
}
//判断是否⽀持⾃定义的Token,从⽽决定是否需要进⾏认证校验,当使⽤accountLoginToken进⾏校验
@Override
public boolean supports(Class<?> authentication){
return AccountLoginToken.class.isAssignableFrom(authentication);
}
}
第三步:将AccountAuthenticationProvider放⼊到authenticationProviders中,参考第⼀篇
@Override
protected void configure(AuthenticationManagerBuilder auth)throws Exception {
auth.userDetailsService(securityUserDetailsService);
auth.authenticationProvider(adminSmsAuthenticationProvider);
auth.authenticationProvider(adminPwdAuthenticationProvider);
auth.authenticationProvider(accountAuthenticationProvider);
}
第四步:创建AccountGranter,继承AbstractTokenGranter
该类主要进⾏的就是封装的token传⼊到security的authenticationManager进⾏校验动作,在上⼀篇也描述了,密码模式采⽤的UserPassword**Token进⾏处理,这⾥我们需要修改成⾃定义的AccountLoginToken传⼊到authenticationManager中。AbstractTokenGranter中的granter⽅法,主要先校验clientId,后校验⽤户信息。所以我们主要重写的getOAuth2Authentication⽅法,进⾏后续处理
ample.customoauth.authentication.account;
import org.springframework.security.authentication.*;
import org.Authentication;
import org.springframework.ptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.ken.AbstractTokenGranter;
import org.springframework.security.ken.AuthorizationServerTokenServices;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* ⾃定义模式
* @author : Windy
* @version :1.0
* @since : 2020/12/28 15:47
*/
public class AccountGranter extends AbstractTokenGranter {
/
/模式名称
public static final String GRANT_TYPE ="account_mobile";
private final AuthenticationManager authenticationManager;
public AccountGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFact ory, AuthenticationManager authenticationManager){
super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest){
Map<String, String> parameters =new LinkedHashMap<>(RequestParameters());
//此处map为controller封装的参数map,名字也可以⾃⼰定义
//从参数中获取⼿机号
String mobile = ("username");
//参数中获取密码
String password = ("password");
//参数中获取密码
String vcode = ("vcode");
//删除参数中的两项(这⾥我暂时还不清楚为啥要删除,不清楚这个传输会引起什么问题,源码中删除那么我先暂时删除)        ve("password");
//暂时将三个全部传⼊
Authentication userAuth =new AccountLoginToken(mobile, password,vcode);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try{
userAuth = authenticationManager.authenticate(userAuth);
}catch(AccountStatusException | BadCredentialsException ase){
//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
throw new Message());
web端登录
}
// If the username/password are wrong the spec says we should send 400/invalid grant
if(userAuth == null ||!userAuth.isAuthenticated()){
throw new InvalidGrantException("校验失败: "+ mobile);
}
//验证通过,则返回⼀个Oauth2token。
OAuth2Request storedOAuth2Request =getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
第五步:将AccountGranter放⼊到GranterList中

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。