首先只实现账号密码的登录模式。
package com.whq.security.oauth.config;
import com.whq.security.oauth.provider.SMSAuthenticationProvider;
import com.whq.security.oauth.service.MyUserDetailsService;
import com.whq.security.oauth.service.SMSUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* spring security 相关配置
*/
@Configuration
public class WhqSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//1.配置基本认证方式
http.authorizeRequests()
// 角色为“ADMIN”的用户才可以访问/test/admin/相关的接口
//.antMatchers("/test/admin/**").hasRole("ADMIN")
// 角色为"USER"、“ADMIN”的用户才可以访问/test/user/相关的接口
//.antMatchers("/test/user/**").hasAnyRole("USER", "ADMIN")
// 所有用户都可以访问的接口
.antMatchers("/swagger/**", "/oauth/token/**").permitAll()
// 对任意请求都进行认证(其他路径的请求登录后才可以访问)
.anyRequest()
.authenticated()
//开启basic认证
.and().httpBasic()
.and().formLogin();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 初始化security的认证管理器
@Bean("authenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 账号密码登录设置获取用户UserDetailsService
// 如果所有登录方式获取用户的方式一致,则可以在AuthorizationServerConfigurerAdapter的AuthorizationServerEndpointsConfigurer中设置UserDetailsService
@Bean
public DaoAuthenticationProvider getDaoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(myUserDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(getDaoAuthenticationProvider());
}
}
package com.whq.security.oauth.config;
import com.whq.security.oauth.granter.WhqTokenGranter;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.ArrayList;
import java.util.List;
/**
* spring security oauth2 相关配置
*/
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
private final AuthenticationManager authenticationManager;
private final PasswordEncoder passwordEncoder;
private final TokenStore tokenStore;
private final TokenEnhancer jwtTokenEnhancer;
private final JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
//获取自定义tokenGranter
TokenGranter tokenGranter = WhqTokenGranter.getTokenGranter(authenticationManager, endpoints);
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.tokenGranter(tokenGranter);
// 扩展token返回结果
if (jwtAccessTokenConverter != null && jwtTokenEnhancer != null) {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List enhancerList = new ArrayList<>();
enhancerList.add(jwtTokenEnhancer);
enhancerList.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(enhancerList);
// jwt增强
endpoints.tokenEnhancer(tokenEnhancerChain).accessTokenConverter(jwtAccessTokenConverter);
}
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 将client-secret存储在内存中
// 如果需要从数据库加载则继承JdbcClientDetailsService类自定义处理
// clients.withClientDetails();
// basic验证用户名:whqClient 密码:whqSecret
clients.inMemory().withClient("whqClient").secret(passwordEncoder.encode("whqSecret"))
// 令牌有效时间,单位秒
.accessTokenValiditySeconds(7200)
// 支持账号密码模式登录
.authorizedGrantTypes("refresh_token", "pwd")
// 权限有哪些,如果这两配置了该参数,客户端发请求可以不带参数,使用配置的参数
.scopes("all", "read", "write");
}
}
package com.whq.security.oauth.config;
import com.whq.security.oauth.factory.JwtTokenEnhancer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* jwt 生成token相关配置
*/
@Configuration
public class JwtTokenStoreConfiguration {
/**
* 使用jwtTokenStore存储token
*/
@Bean
public TokenStore jwtTokenStore() {
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* 用于生成jwt
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("whq");
return accessTokenConverter;
}
/**
* 用于扩展jwt
*/
@Bean
@ConditionalOnMissingBean(name = "jwtTokenEnhancer")
public TokenEnhancer jwtTokenEnhancer(JwtAccessTokenConverter jwtAccessTokenConverter) {
return new JwtTokenEnhancer(jwtAccessTokenConverter);
}
}
package com.whq.security.oauth.factory;
import com.whq.security.oauth.user.MyUser;
import lombok.AllArgsConstructor;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.HashMap;
import java.util.Map;
@AllArgsConstructor
public class JwtTokenEnhancer implements TokenEnhancer {
private final JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
MyUser principal = (MyUser) authentication.getUserAuthentication().getPrincipal();
// token参数增强
// todo 以下可根据自身业务自由设置,方便于解析token后获取到有价值的信息
Map info = new HashMap<>(16);
info.put("user_id", principal.getUserId());
info.put("user_name", principal.getUsername());
info.put("user_phone", "18788888888");
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
OAuth2AccessToken oAuth2AccessToken = jwtAccessTokenConverter.enhance(accessToken, authentication);
String tokenValue = oAuth2AccessToken.getValue();
String tenantId = principal.getTenantId();
String userId = principal.getUserId() == null ? "" : principal.getUserId().toString();
// todo 此处可以将token存入redis
// RedisTokenUtil.addAccessToken();
return accessToken;
}
}
package com.whq.security.oauth.granter;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.CompositeTokenGranter;
import org.springframework.security.oauth2.provider.TokenGranter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 自定义拓展TokenGranter
*/
public class WhqTokenGranter {
/**
* 自定义tokenGranter
*/
public static TokenGranter getTokenGranter(final AuthenticationManager authenticationManager, final AuthorizationServerEndpointsConfigurer endpoints) {
// 默认tokenGranter集合
List granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
// 账号密码模式
granters.add(new PasswordGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
// 组合tokenGranter集合
return new CompositeTokenGranter(granters);
}
}
package com.whq.security.oauth.granter;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
public class PasswordGranter extends AbstractTokenGranter {
public static final String GRANT_TYPE = "pwd";
private final AuthenticationManager authenticationManager;
public PasswordGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected PasswordGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String username = parameters.get("username");
String cryptPassword = parameters.get("password");
Authentication userAuth = new UsernamePasswordAuthenticationToken(username, cryptPassword);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
// 以下代码为spring security 授权认证逻辑
userAuth = authenticationManager.authenticate(userAuth);
} catch (AccountStatusException | BadCredentialsException ase) {
throw new InvalidGrantException(ase.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + username);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
此处设置加载用户信息的方式,账号密码使用登录账号获取用户信息。
package com.whq.security.oauth.service;
import com.whq.security.oauth.user.MyUser;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
// 后续登录使用此方法加载用户信息
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
MyUser result = MyUser.getUser(username);
// 模拟的用户查询数据为明文密码,实际使用时都是加密存储,此处手动加密模拟处理
String encode = passwordEncoder.encode(result.getPassword());
// 用户不存在,抛出异常
if (result == null) {
throw new UsernameNotFoundException("用户不存在");
}
MyUser res = new MyUser(result.getUsername(), encode);
return res;
}
}
此处实体类继承自org.springframework.security.core.userdetails.User
package com.whq.security.oauth.user;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* 用户信息实体类,实际项目中根据自身项目需求修改
*/
@Getter
@Setter
public class MyUser extends User {
/**
* 用户id
*/
private final Long userId;
/**
* 租户ID
*/
private final String tenantId;
// 用户构造函数
public MyUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection extends GrantedAuthority> authorities, Long userId, String tenantId) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.userId = userId;
this.tenantId = tenantId;
}
/**
* 方便测试用的构造函数
* 除用户名及密码外其他信息一致
*/
public MyUser(String username, String password) {
// authorities 参数为用户角色相关,如果没有使用spring security进行角色权限控制,则不需要配置此参数
this(username, password, true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("USER,AMDIN"), 1L, "000000");
}
// 模拟数据库存储的用户信息
public static Map myUsers;
static {
myUsers = new HashMap<>();
myUsers.put("whq1", new MyUser("whq1", "123"));
myUsers.put("whq2", new MyUser("whq2", "123"));
myUsers.put("18788888888", new MyUser("whq1", "123"));
myUsers.put("18666666666", new MyUser("whq2", "123"));
}
// 模拟数据库通过用户名查询用户信息
public static MyUser getUser(String userName) {
return myUsers.get(userName);
}
// 模拟数据库通过手机号查询用户信息
public static MyUser getUserByPhone(String userName) {
return myUsers.get(userName);
}
}
package com.whq.security.oauth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.whq")
public class SecurityOAuth2Application {
public static void main(String[] args) {
SpringApplication.run(SecurityOAuth2Application.class, args);
}
}
以上所有代码就实现了账号密码模式的登录。下来通过postman进行验证:
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 将client-secret存储在内存中
// 如果需要从数据库加载则继承JdbcClientDetailsService类自定义处理
// clients.withClientDetails();
// basic验证用户名:whqClient 密码:whqSecret
clients.inMemory().withClient("whqClient").secret(passwordEncoder.encode("whqSecret"))
// 令牌有效时间,单位秒
.accessTokenValiditySeconds(7200)
// 支持短信验证码、账号密码模式登录
.authorizedGrantTypes("refresh_token", "SMSVerification", "pwd")
// 权限有哪些,如果这两配置了该参数,客户端发请求可以不带参数,使用配置的参数
.scopes("all", "read", "write");
}
package com.whq.security.oauth.config;
import com.whq.security.oauth.provider.SMSAuthenticationProvider;
import com.whq.security.oauth.service.MyUserDetailsService;
import com.whq.security.oauth.service.SMSUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* spring security 相关配置
*/
@Configuration
public class WhqSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
// 短信验证时获取用户方式改变
@Autowired
private SMSUserDetailsService smsUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
//1.配置基本认证方式
http.authorizeRequests()
// 角色为“ADMIN”的用户才可以访问/test/admin/相关的接口
//.antMatchers("/test/admin/**").hasRole("ADMIN")
// 角色为"USER"、“ADMIN”的用户才可以访问/test/user/相关的接口
//.antMatchers("/test/user/**").hasAnyRole("USER", "ADMIN")
// 所有用户都可以访问的接口
.antMatchers("/swagger/**", "/oauth/token/**").permitAll()
// 对任意请求都进行认证(其他路径的请求登录后才可以访问)
.anyRequest()
.authenticated()
//开启basic认证
.and().httpBasic()
.and().formLogin();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 初始化security的认证管理器
@Bean("authenticationManager")
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// 账号密码登录设置获取用户UserDetailsService
// 如果所有登录方式获取用户的方式一致,则可以在AuthorizationServerConfigurerAdapter的AuthorizationServerEndpointsConfigurer中设置UserDetailsService
@Bean
public DaoAuthenticationProvider getDaoAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(myUserDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
return daoAuthenticationProvider;
}
// 短信验证provider初始化
@Bean
public SMSAuthenticationProvider getSMSAuthenticationProvider(){
SMSAuthenticationProvider smsAuthenticationProvider = new SMSAuthenticationProvider();
smsAuthenticationProvider.setUserDetailsService(smsUserDetailsService);
return smsAuthenticationProvider;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(getDaoAuthenticationProvider());
// 添加短信验证provider
auth.authenticationProvider(getSMSAuthenticationProvider());
}
}
package com.whq.security.oauth.granter;
import com.whq.security.oauth.token.SMSVerificationAuthenticationToken;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import java.util.LinkedHashMap;
import java.util.Map;
public class SMSVerificationTokenGranter extends AbstractTokenGranter {
private static final String GRANT_TYPE = "SMSVerification";
private final AuthenticationManager authenticationManager;
public SMSVerificationTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory) {
this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
}
protected SMSVerificationTokenGranter(AuthenticationManager authenticationManager,
AuthorizationServerTokenServices tokenServices,
ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory,
String grantType) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.authenticationManager = authenticationManager;
}
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map parameters = new LinkedHashMap(tokenRequest.getRequestParameters());
String smsCode = parameters.get("SMS-Code");
String phone = parameters.get("phone");
parameters.remove("SMS-Code");
Authentication userAuth = new SMSVerificationAuthenticationToken(phone, smsCode);
((AbstractAuthenticationToken) userAuth).setDetails(parameters);
try {
// 以下代码为spring security 授权认证逻辑
userAuth = authenticationManager.authenticate(userAuth);
} catch (AccountStatusException | BadCredentialsException ase) {
throw new InvalidGrantException(ase.getMessage());
}
if (userAuth == null || !userAuth.isAuthenticated()) {
throw new InvalidGrantException("Could not authenticate user: " + phone);
}
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
}
WhqTokenGranter类中对SMSVerificationTokenGranter进行初始化
public static TokenGranter getTokenGranter(final AuthenticationManager authenticationManager, final AuthorizationServerEndpointsConfigurer endpoints) {
// 默认tokenGranter集合
List granters = new ArrayList<>(Collections.singletonList(endpoints.getTokenGranter()));
// 账号密码模式
granters.add(new PasswordGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
// 短信验证码模式
granters.add(new SMSVerificationTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory()));
// 组合tokenGranter集合
return new CompositeTokenGranter(granters);
}
package com.whq.security.oauth.provider;
import com.whq.security.oauth.token.SMSVerificationAuthenticationToken;
import com.whq.security.oauth.user.MyUser;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
import java.util.ArrayList;
public class SMSAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
/**
* 短信认证
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取手机号
String phone = (authentication.getPrincipal() == null ) ? null : authentication.getPrincipal().toString();
// 获取短信验证码
String smsCode = (authentication.getCredentials() == null ) ? null : authentication.getCredentials().toString();
// todo 在此处进行短信验证码的校验
// 此处模拟短信验证码为123
if (!"123".equals(smsCode)) {
throw new UserDeniedAuthorizationException("验证码不正确");
}
MyUser userDetails = (MyUser) userDetailsService.loadUserByUsername(phone);
if (userDetails == null) {
throw new InternalAuthenticationServiceException("当前手机号不存在,请先注册");
}
SMSVerificationAuthenticationToken smsVerificationAuthenticationToken = new SMSVerificationAuthenticationToken(new ArrayList(), userDetails, authentication.getCredentials());
smsVerificationAuthenticationToken.setDetails(authentication.getDetails());
return smsVerificationAuthenticationToken;
}
// 判断是否支持此登录方式
@Override
public boolean supports(Class> authentication) {
return (SMSVerificationAuthenticationToken.class.isAssignableFrom(authentication));
}
}
此处如果账号密码获取用户信息的方式与短信验证码相同(比如:账号密码登录时也是通过手机号登录)则无需再添加此类,同时WhqSecurityConfig类中的配置只需要给SMSAuthenticationProvider设置相同的UserDetailsService即可。
package com.whq.security.oauth.service;
import com.whq.security.oauth.user.MyUser;
import org.springframework.beans.factory.annotation.Autowired;
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;
@Service
public class SMSUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
// 后续登录使用此方法加载用户信息
@Override
public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
// 用户使用手机号登录
MyUser result = MyUser.getUserByPhone(phone);
// 模拟的用户查询数据为明文密码,实际使用时都是加密存储,此处手动加密模拟处理
String encode = passwordEncoder.encode(result.getPassword());
// 用户不存在,抛出异常
if (result == null) {
throw new UsernameNotFoundException("用户不存在");
}
MyUser res = new MyUser(result.getUsername(), encode);
return res;
}
}
package com.whq.security.oauth.token;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
public class SMSVerificationAuthenticationToken extends AbstractAuthenticationToken {
// 存储登录手机号
private Object principal;
// 存储登录短信验证码
private Object credentials;
public SMSVerificationAuthenticationToken(Object principal, Object credentials) {
super(null);
this.principal = principal;
this.credentials = credentials;
super.setAuthenticated(false);
}
/**
* 校验通过时使用此构造函数
* 设置参数super.setAuthenticated(true); 表示短信验证码登录校验通过,准备生成token
*/
public SMSVerificationAuthenticationToken(Collection extends GrantedAuthority> authorities, Object principal, Object credentials) {
super(authorities);
this.principal = principal;
this.credentials = credentials;
// 设置校验通过
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return this.credentials;
}
@Override
public Object getPrincipal() {
return this.principal;
}
}
springOAuth2添加新的认证模式需要完成如下工作:
基于上述示例,通过DEBUG对springSecurity和springOAuth2进行源码分析,文章链接地址如下:
springSecurity及springOAuth2源码解析
文章内容结合实际工作经验编写,如有问题,欢迎大家评论指正。