Merge branch '1.x' into 2.x

# Conflicts:
#	README.md
#	UPDATE.md
#	doc/MENU.md
#	pom.xml
#	yexuejc-springboot-base/pom.xml
This commit is contained in:
maxf 2018-12-03 10:52:32 +08:00
commit 40c1472104
25 changed files with 475 additions and 169 deletions

View File

@ -1,5 +1,7 @@
Security框架封装集成登录 使用指南
Security框架封装集成多方登录 使用指南
-------------
#### 先上[效果图](Securtity效果图.md)
单独使用例子工程:[https://github.com/yexuejc/springboot-security-login-simple](https://github.com/yexuejc/springboot-security-login-simple)
* 本项目依赖不向下传递
@ -15,7 +17,8 @@ Security框架封装集成登录 使用指南
</dependencies>
```
> **相关文件说明** 所有核心文件都在 com.yexuejc.springboot.base.security 包下
#### 现附上系统实现逻辑图
![多方登录系统实现逻辑图](多方登录设计.jpg)
1.com.yexuejc.springboot.base.security.SecurityConfig
<br/>
@ -26,6 +29,38 @@ Security框架封装集成登录 使用指南
* 继承configure(HttpSecurity http) 完善更多security过滤配置
* 例子[com.yexuejc.springboot.base.security.MySecurityConfig](../yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java)
#### 注: 代码中抛出的相关异常拦截在filter.setAuthenticationFailureHandler()中处理,参考[MySecurityConfig](../yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java)
```
filter.setAuthenticationFailureHandler((request, response, exception) -> {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpStatus.UNAUTHORIZED.value());
Resps resps = new Resps();
if (exception instanceof DisabledException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_IS_LOCK_MSG});
} else if (exception instanceof AccountExpiredException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_IS_EXPIRE_MSG});
} else if (exception instanceof CredentialsExpiredException) {
resps.setErr(BizConsts.BASE_LOGIN_IS_EXPIRE_CODE, new String[]{BizConsts.BASE_LOGIN_IS_EXPIRE_MSG});
} else if (exception instanceof LockedException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_IS_LOCKED_MSG});
} else if (exception instanceof AuthenticationCredentialsNotFoundException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_CREDENTIALS_NOT_FOUND_MSG});
} else if (exception instanceof ThirdPartyAuthorizationException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{exception.getMessage()});
} else if (exception instanceof BadCredentialsException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_PWD_IS_ERR_MSG});
} else if (exception instanceof UsernameNotFoundException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_ACCOUNT_NOT_FOUND_MSG});
} else if (exception instanceof UserNotAuthoriayException) {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{exception.getMessage()});
} else {
resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_SYS_ERR_MSG});
}
response.getWriter().write(JsonUtil.obj2Json(resps));
response.getWriter().close();
});
```
2.com.yexuejc.springboot.base.security.UserDetailsManager
<br/>
**获取登录用户信息**

24
doc/Securtity效果图.md Normal file
View File

@ -0,0 +1,24 @@
Security 多方登录封装使用效果图
---------------
### 账号登录
密码错误
![](sl/sl_02.png)
正确
![](sl/sl_01.png)
### 短信登录
发送短信
![](sl/sl_ss.png)
短信错误
![](sl/sl_err.jpg)
短信过期
![](sl/sl_gq.png)
正确
![](sl/sl_10.png)
### 第三方登录
第一次登录,需要绑定手机号
![](sl/sl_t3.png)
绑定过手机号的第三方账号登录(绑定相关业务需要自己实现)
![](sl/sl_t4.png)

BIN
doc/sl/sl_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
doc/sl/sl_02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
doc/sl/sl_10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
doc/sl/sl_err.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
doc/sl/sl_gq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
doc/sl/sl_ss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
doc/sl/sl_t3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
doc/sl/sl_t4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
doc/多方登录设计.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

View File

@ -0,0 +1,24 @@
package com.yexuejc.springboot.base.exception;
/**
* 类型转换异常
*
* @author: maxf
* @date: 2018/5/12 18:36
*/
public class ClassConvertExeption extends RuntimeException {
private static final long serialVersionUID = -2390195902982826130L;
/**
* 错误码
*/
private String code = "E";
public ClassConvertExeption() {
super("类型转换异常");
}
public ClassConvertExeption(String message) {
super(message);
}
}

View File

@ -8,6 +8,7 @@ import com.yexuejc.base.util.StrUtil;
import com.yexuejc.springboot.base.exception.GatewayException;
import com.yexuejc.springboot.base.util.LogUtil;
import org.apache.commons.io.IOUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
@ -32,6 +33,7 @@ import java.security.interfaces.RSAPrivateKey;
* @date: 2018/5/12 22:49
*/
@ControllerAdvice
@ConditionalOnClass({RequestBodyAdvice.class, HttpHeaders.class, HttpInputMessage.class, HttpMessageConverter.class})
@EnableConfigurationProperties(RsaProperties.class)
public class ParamsRequestBodyAdvice implements RequestBodyAdvice {

View File

@ -6,6 +6,7 @@ import com.yexuejc.base.http.Resps;
import com.yexuejc.base.util.JsonUtil;
import com.yexuejc.base.util.StrUtil;
import com.yexuejc.springboot.base.util.LogUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
@ -29,6 +30,7 @@ import java.util.Map;
* @date: 2018/5/12 22:50
*/
@ControllerAdvice
@ConditionalOnClass({ResponseBodyAdvice.class, ServerHttpRequest.class, ServerHttpResponse.class, MediaType.class})
@EnableConfigurationProperties(RsaProperties.class)
public class ParamsResponseBodyAdvice implements ResponseBodyAdvice {

View File

@ -20,37 +20,37 @@ public class ConsumerAuthenticationProcessingFilter extends AbstractAuthenticati
// ~ Static fields/initializers
// =====================================================================================
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
protected static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
protected static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
/**
* 登录方式
*/
public static final String SPRING_SECURITY_FORM_LOGTYPE_KEY = "logtype";
public static final String SPRING_SECURITY_FORM_OPENID_KEY = "openid";
protected static final String SPRING_SECURITY_FORM_LOGTYPE_KEY = "logtype";
protected static final String SPRING_SECURITY_FORM_OPENID_KEY = "openid";
/********************************** 第三方登录时附带信息*************************************/
/**
* 头像
*/
public static final String SPRING_SECURITY_FORM_HEAD_KEY = "head";
protected static final String SPRING_SECURITY_FORM_HEAD_KEY = "head";
/**
* 昵称
*/
public static final String SPRING_SECURITY_FORM_NICKNAME_KEY = "nickname";
protected static final String SPRING_SECURITY_FORM_NICKNAME_KEY = "nickname";
/**
* 性别
*/
public static final String SPRING_SECURITY_FORM_SEX_KEY = "sex";
protected static final String SPRING_SECURITY_FORM_SEX_KEY = "sex";
/********************************** 第三方登录时附带信息*************************************/
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private String logtypeParameter = SPRING_SECURITY_FORM_LOGTYPE_KEY;
private String openidParameter = SPRING_SECURITY_FORM_OPENID_KEY;
private String headParameter = SPRING_SECURITY_FORM_HEAD_KEY;
private String nicknameParameter = SPRING_SECURITY_FORM_NICKNAME_KEY;
private String sexParameter = SPRING_SECURITY_FORM_SEX_KEY;
private boolean postOnly = true;
private boolean reverse = true;
protected String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
protected String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
protected String logtypeParameter = SPRING_SECURITY_FORM_LOGTYPE_KEY;
protected String openidParameter = SPRING_SECURITY_FORM_OPENID_KEY;
protected String headParameter = SPRING_SECURITY_FORM_HEAD_KEY;
protected String nicknameParameter = SPRING_SECURITY_FORM_NICKNAME_KEY;
protected String sexParameter = SPRING_SECURITY_FORM_SEX_KEY;
protected boolean postOnly = true;
protected boolean reverse = true;
// ~ Constructors
// ===================================================================================================

View File

@ -4,6 +4,7 @@ import com.yexuejc.base.pojo.ApiVO;
import com.yexuejc.base.util.StrUtil;
import com.yexuejc.springboot.base.constant.BizConsts;
import com.yexuejc.springboot.base.constant.LogTypeConsts;
import com.yexuejc.springboot.base.exception.ClassConvertExeption;
import com.yexuejc.springboot.base.exception.ThirdPartyAuthorizationException;
import com.yexuejc.springboot.base.security.inte.User;
import com.yexuejc.springboot.base.security.inte.UserService;
@ -50,12 +51,12 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic
* PasswordEncoder#matches(CharSequence, String)} on when the user is
* not found to avoid SEC-2056.
*/
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
protected static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
// ~ Instance fields
// ================================================================================================
private PasswordEncoder passwordEncoder;
protected PasswordEncoder passwordEncoder;
/**
* The password used to perform
@ -64,10 +65,10 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic
* {@link PasswordEncoder} implementations will short circuit if the password is not
* in a valid format.
*/
private volatile String userNotFoundEncodedPassword;
protected volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private final UserService accountView;
protected UserDetailsService userDetailsService;
protected final UserService accountView;
public ConsumerAuthenticationProvider(UserDetailsService userDetailsService, UserService accountView) {
@ -177,46 +178,14 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic
throw notFound;
} else {
try {
//其他方式登录:查询账号 没有->创建账号
//第三方登录
if (consumerToken != null && StrUtil.isNotEmpty(consumerToken.getOpenid())) {
ApiVO apiVO = accountView.checkOpenId(consumerToken);
if (apiVO.isSucc()) {
//已有账号
User consumer = apiVO.getObject1(User.class);
// 处理用户权限
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : consumer.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
loadedUser = new ConsumerUser(
StrUtil.isEmpty(consumer.getMobile()) ? consumerToken.getOpenid() : consumer.getMobile(),
consumer.getPwd(), consumer.getEnable(), consumer.getNonExpire(),
true, consumer.getNonLock(), authorities, consumer.getConsumerId(),
logtype, System.currentTimeMillis());
return loadedUser;
}
}
//第三方登录+短信登录
if (consumerToken != null) {
//没有->创建账号
consumerToken.isReg = true;
ApiVO apiVO = accountView.addConsumer(consumerToken);
if (apiVO.isSucc()) {
loadedUser = display(consumerToken, apiVO.getObject1(User.class));
return loadedUser;
} else {
throw new ThirdPartyAuthorizationException(apiVO.getMsg());
}
}
return third(consumerToken, loadedUser, logtype);
} catch (Exception e) {
e.printStackTrace();
if (e instanceof ThirdPartyAuthorizationException) {
throw e;
}
throw new ThirdPartyAuthorizationException("登录失败,请稍后重试");
throw new ThirdPartyAuthorizationException(e.getMessage());
}
throw notFound;
}
} catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(
@ -229,13 +198,78 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic
return loadedUser;
}
private void prepareTimingAttackProtection() {
/**
* 第三方登录处理=>登录用户为空此方法处理返回登录用户
*
* @param consumerToken 登录信息
* @param loadedUser 登录用户为空时进入此方法
* @param logtype 登录方式
* @return 登录用户
*/
protected UserDetails third(ConsumerToken consumerToken, UserDetails loadedUser, String logtype) {
//其他方式登录:查询账号 没有->创建账号
//第三方登录
if (consumerToken != null && StrUtil.isNotEmpty(consumerToken.getOpenid())) {
Object obj = accountView.checkOpenId(consumerToken);
if (obj != null) {
//已有账号
if (obj instanceof User) {
User consumer = (User) obj;
// 处理用户权限
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : consumer.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
loadedUser = new ConsumerUser(
StrUtil.isEmpty(consumer.getMobile()) ? consumerToken.getOpenid() : consumer.getMobile(),
consumer.getPwd(), consumer.getEnable(), consumer.getNonExpire(),
true, consumer.getNonLock(), authorities, consumer.getConsumerId(),
logtype, System.currentTimeMillis());
return loadedUser;
} else if (obj instanceof UserDetails) {
loadedUser = (UserDetails) obj;
return loadedUser;
} else {
throw new ClassConvertExeption("获取登录用户信息返回结果类型必须是com.yexuejc.springboot.base.security.inte.User实现类" +
"或者org.springframework.security.core.userdetails.UserDetails实现类" +
"或者com.yexuejc.springboot.base.security.ConsumerUser继承类");
}
}
}
//第三方登录+短信登录
if (consumerToken != null) {
//没有->创建账号
consumerToken.isReg = true;
Object obj = accountView.addConsumer(consumerToken);
if (obj != null) {
if (obj instanceof User) {
User consumer = (User) obj;
loadedUser = display(consumerToken, consumer);
return loadedUser;
} else if (obj instanceof UserDetails) {
loadedUser = (UserDetails) obj;
return loadedUser;
} else {
throw new ClassConvertExeption("获取登录用户信息返回结果类型必须是com.yexuejc.springboot.base.security.inte.User实现类" +
"或者org.springframework.security.core.userdetails.UserDetails实现类" +
"或者com.yexuejc.springboot.base.security.ConsumerUser继承类");
}
} else {
throw new ThirdPartyAuthorizationException("第三方登录失败");
}
} else {
throw new ThirdPartyAuthorizationException();
}
}
protected void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
protected void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, "{MD5}" + this.userNotFoundEncodedPassword);
@ -249,7 +283,7 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic
* @param consumer 实际用户信息
* @return response User
*/
private UserDetails display(ConsumerToken consumerToken, User consumer) {
protected UserDetails display(ConsumerToken consumerToken, User consumer) {
// 处理用户权限
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : consumer.getRoles()) {

View File

@ -34,10 +34,10 @@ import java.util.Map;
*/
public class ConsumerSecurityContextRepository implements SecurityContextRepository {
protected final Log logger = LogFactory.getLog(this.getClass());
private static final String TOKEN = "token";
private static final String ROLES = "roles";
protected static final String TOKEN = "token";
protected static final String ROLES = "roles";
private final RedisTemplate<Object, Object> redisTemplate0;
protected final RedisTemplate<Object, Object> redisTemplate0;
public ConsumerSecurityContextRepository(RedisTemplate<Object, Object> redisTemplate0) {
this.redisTemplate0 = redisTemplate0;

View File

@ -1,12 +1,10 @@
package com.yexuejc.springboot.base.security;
import com.yexuejc.springboot.base.autoconfigure.MutiRedisAutoConfiguration;
import com.yexuejc.springboot.base.security.inte.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@ -17,6 +15,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@ -39,10 +38,6 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
return super.authenticationManagerBean();
}
@Autowired
@Qualifier(MutiRedisAutoConfiguration.BEAN_REDIS_TEMPLATE0)
private RedisTemplate<Object, Object> redisTemplate0;
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
@ -58,22 +53,51 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetailsService userDetailsService = new UserDetailsManager(getUserService());
auth.authenticationProvider(new ConsumerAuthenticationProvider(userDetailsService, getUserService()));
UserDetailsService userDetailsService = createUserDetailsManager();
AuthenticationProvider authenticationProvider = createConsumerAuthenticationProvider(userDetailsService);
auth.authenticationProvider(authenticationProvider);
auth.userDetailsService(userDetailsService);
}
/**
* 初始化 AuthenticationProvider
*
* @param userDetailsService
* @return
*/
protected AuthenticationProvider createConsumerAuthenticationProvider(UserDetailsService userDetailsService) {
return new ConsumerAuthenticationProvider(userDetailsService, getUserService());
}
/**
* 初始化 UserDetailsService
*
* @return
*/
protected UserDetailsService createUserDetailsManager() {
return new UserDetailsManager(getUserService());
}
@Bean
public ConsumerAuthenticationProcessingFilter consumerAuthenticationProcessingFilter(
AuthenticationManager authenticationManager) throws Exception {
ConsumerAuthenticationProcessingFilter filter = new ConsumerAuthenticationProcessingFilter
(authenticationManager);
ConsumerAuthenticationProcessingFilter filter = createConsumerAuthenticationProcessingFilter(authenticationManager);
filter.setAuthenticationManager(this.authenticationManager());
loginHodler(filter);
return filter;
}
/**
* 初始化 ConsumerAuthenticationProcessingFilter
*
* @param authenticationManager
* @return
*/
protected ConsumerAuthenticationProcessingFilter createConsumerAuthenticationProcessingFilter(AuthenticationManager authenticationManager) {
return new ConsumerAuthenticationProcessingFilter(authenticationManager);
}
/**
* <pre>
* 处理登录
@ -88,8 +112,7 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint() {
LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint
("/login");
LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
return loginUrlAuthenticationEntryPoint;
}
@ -134,7 +157,7 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
.cors()
.and().servletApi().disable()
.requestCache().disable()
.securityContext().securityContextRepository(new ConsumerSecurityContextRepository(redisTemplate0))
.securityContext().securityContextRepository(createConsumerSecurityContextRepository())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
@ -142,6 +165,22 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
UsernamePasswordAuthenticationFilter.class);
}
/**
* 创建 SecurityContextRepository
*
* @return
*/
protected SecurityContextRepository createConsumerSecurityContextRepository() {
return new ConsumerSecurityContextRepository(getRedisDB());
}
/**
* 初始化放置用户信息的reids库
*
* @return
*/
protected abstract RedisTemplate<Object, Object> getRedisDB();
@Override
public void configure(WebSecurity web) throws Exception {

View File

@ -1,6 +1,7 @@
package com.yexuejc.springboot.base.security;
import com.yexuejc.base.util.StrUtil;
import com.yexuejc.springboot.base.exception.ClassConvertExeption;
import com.yexuejc.springboot.base.exception.UserNotAuthoriayException;
import com.yexuejc.springboot.base.security.inte.User;
import com.yexuejc.springboot.base.security.inte.UserService;
@ -23,7 +24,7 @@ import java.util.List;
*/
public class UserDetailsManager extends InMemoryUserDetailsManager {
private final UserService userService;
protected final UserService userService;
public UserDetailsManager(UserService userService) {
this.userService = userService;
@ -31,22 +32,34 @@ public class UserDetailsManager extends InMemoryUserDetailsManager {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User consumer = userService.getConsumerByUserName(username);
if (StrUtil.isEmpty(consumer)) {
throw new UsernameNotFoundException(username);
return loadUser(username);
}
protected ConsumerUser loadUser(String username) throws UsernameNotFoundException{
Object user = userService.getConsumerByUserName(username);
if (user instanceof User) {
User consumer = (User) user;
if (StrUtil.isEmpty(consumer)) {
throw new UsernameNotFoundException(username);
}
// 处理用户权限
List<GrantedAuthority> authorities = new ArrayList<>();
if (StrUtil.isEmpty(consumer.getRoles())) {
throw new UserNotAuthoriayException("用户" + username + "缺少权限");
}
for (String role : consumer.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
ConsumerUser consumerUser = new ConsumerUser(consumer.getMobile(), consumer.getPwd(),
consumer.getEnable(), consumer.getNonExpire(), true, consumer.getNonLock(),
authorities, consumer.getConsumerId(), null, System.currentTimeMillis());
return consumerUser;
} else if (user instanceof ConsumerUser) {
return (ConsumerUser) user;
} else {
throw new ClassConvertExeption("获取登录用户信息返回结果类型必须是com.yexuejc.springboot.base.security.inte.User实现类" +
"或者com.yexuejc.springboot.base.security.ConsumerUser继承类");
}
// 处理用户权限
List<GrantedAuthority> authorities = new ArrayList<>();
if (StrUtil.isEmpty(consumer.getRoles())) {
throw new UserNotAuthoriayException("用户" + username + "缺少权限");
}
for (String role : consumer.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
ConsumerUser consumerUser = new ConsumerUser(consumer.getMobile(), consumer.getPwd(),
consumer.getEnable(), consumer.getNonExpire(), true, consumer.getNonLock(),
authorities, consumer.getConsumerId(), null, System.currentTimeMillis());
return consumerUser;
}
}

View File

@ -3,6 +3,8 @@ package com.yexuejc.springboot.base.security.inte;
import java.util.List;
/**
* 登录用户接口
*
* @author maxf
* @version 1.0
* @ClassName User

View File

@ -2,9 +2,13 @@ package com.yexuejc.springboot.base.security.inte;
import com.yexuejc.base.pojo.ApiVO;
import com.yexuejc.springboot.base.constant.BizConsts;
import com.yexuejc.springboot.base.exception.UserNotAuthoriayException;
import com.yexuejc.springboot.base.security.ConsumerToken;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
/**
* 用户登录处理接口
*
* @author maxf
* @version 1.0
* @ClassName UserService
@ -14,11 +18,18 @@ import com.yexuejc.springboot.base.security.ConsumerToken;
public interface UserService {
/**
* 根据用户名到数据库查询用户
* <p>
* <p>
* <p>
* 获取登录用户信息返回结果类型必须是
* {@link com.yexuejc.springboot.base.security.inte.User}实现类<br/>
* 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类
* </p>
*
* @param username 登录账号
* @return
*/
User getConsumerByUserName(String username);
Object getConsumerByUserName(String username) throws UserNotAuthoriayException, UsernameNotFoundException;
/**
* 校验短信验证码=>短信登录
@ -32,17 +43,29 @@ public interface UserService {
/**
* 校验第三方登录openid
* <p>
* 获取登录用户信息返回结果类型必须是
* {@link com.yexuejc.springboot.base.security.inte.User}实现类<br/>
* 或者{@link org.springframework.security.core.userdetails.UserDetails}实现类<br/>
* 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类
* </p>
*
* @param consumerToken 登录信息
* @return apiVO.setObject1(User.class) 自己封装登录用户信息
* @return 自己封装登录用户信息
*/
ApiVO checkOpenId(ConsumerToken consumerToken);
Object checkOpenId(ConsumerToken consumerToken);
/**
* 没有账号时根据登录信息创建账号
* <p>
* 获取登录用户信息返回结果类型必须是
* {@link com.yexuejc.springboot.base.security.inte.User}实现类<br/>
* 或者{@link org.springframework.security.core.userdetails.UserDetails}实现类<br/>
* 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类
* </p>
*
* @param consumerToken 登录信息
* @return
* @return 自己封装登录用户信息
*/
ApiVO addConsumer(ConsumerToken consumerToken);
Object addConsumer(ConsumerToken consumerToken);
}

View File

@ -47,6 +47,11 @@ public class MySecurityConfig extends SecurityConfig {
@Qualifier(MutiRedisAutoConfiguration.BEAN_REDIS_TEMPLATE0)
private RedisTemplate<Object, Object> redisTemplate0;
@Override
protected RedisTemplate<Object, Object> getRedisDB() {
return redisTemplate0;
}
/**
* 保存登录信息至redis
*
@ -184,4 +189,5 @@ public class MySecurityConfig extends SecurityConfig {
response.getWriter().close();
});
}
}

View File

@ -13,11 +13,13 @@ import com.yexuejc.springboot.base.constant.LogTypeConsts;
import com.yexuejc.springboot.base.exception.ThirdPartyAuthorizationException;
import com.yexuejc.springboot.base.mapper.ConsumerMapper;
import com.yexuejc.springboot.base.security.domain.Consumer;
import com.yexuejc.springboot.base.security.inte.User;
import com.yexuejc.springboot.base.security.inte.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@ -48,14 +50,37 @@ public class UserServiceImpl implements UserService {
* @return
*/
@Override
public User getConsumerByUserName(String username) {
public Object getConsumerByUserName(String username) {
if (StrUtil.isEmpty(username)) {
throw new UsernameNotFoundException("username为空一般是第三方登录来的直接抛出UsernameNotFoundException就是");
}
QueryWrapper queryWrapper = new QueryWrapper();
queryWrapper.eq("mobile", username);
Consumer consumer = consumerMapper.selectOne(queryWrapper);
ArrayList roles = new ArrayList<>();
if (null == consumer) {
/**
* 1.抛出UsernameNotFoundException这个异常如果是第三方登录会走 {@link #checkOpenId(ConsumerToken)}
* 2.抛出其他Exception可以自己到{@link MySecurityConfig#loginHodler(ConsumerAuthenticationProcessingFilter)}
* 里面的filter.setAuthenticationFailureHandler()中做特殊处理
*/
throw new UsernameNotFoundException("没有该账号相关信息");
}
//h2不支持json人为处理角色
ArrayList roles = new ArrayList<>();
roles.add("ROLE_CONSUMER");
consumer.setRoles(roles);
return consumer;
//1.consumer为User的实现类
// return consumer;
//2. 自己创建ConsumerUser直接返回
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : consumer.getRoles()) {
authorities.add(new SimpleGrantedAuthority(role));
}
ConsumerUser consumerUser = new ConsumerUser(consumer.getMobile(), consumer.getPwd(),
consumer.getEnable(), consumer.getNonExpire(), true, consumer.getNonLock(),
authorities, consumer.getConsumerId(), null, System.currentTimeMillis());
return consumerUser;
}
/**
@ -88,15 +113,14 @@ public class UserServiceImpl implements UserService {
}
/**
* 第三方登录
* 校验openid 根据自己业务做判断
* <br/>
* 返回封装登录用户信息到 apiVO.setObject1(User.class) 自己封装登录用户信息
*
* @param consumerToken 登录信息
* @return
*/
@Override
public ApiVO checkOpenId(ConsumerToken consumerToken) {
public Object checkOpenId(ConsumerToken consumerToken) {
ApiVO apiVO = new ApiVO(ApiVO.STATUS.F, "没有找到用户信息");
switch (consumerToken.getLogtype()) {
case LogTypeConsts.QQ:
@ -111,9 +135,89 @@ public class UserServiceImpl implements UserService {
default:
break;
}
return apiVO;
if (apiVO.isFail()) {
/**
* 未查到:
* 1.返回null会走数据库没有这个openid[第三方账号]信息新增流程 {@link #addConsumer(ConsumerToken)}
* 2.也可以自己创建一个带有特殊标识的ConsumerUser然后在 {@link MySecurityConfig#loginHodler(ConsumerAuthenticationProcessingFilter)}
* 里面的filter.setAuthenticationSuccessHandler()中做特殊处理 ps:假装登录成功 :)
*/
return null;
}
//h2不支持json人为处理角色
ArrayList roles = new ArrayList<>();
roles.add("ROLE_CONSUMER");
apiVO.getObject1(Consumer.class).setRoles(roles);
//根据openid到数据库查到consumer返回
return apiVO.getObject1(Consumer.class);
}
/**
* {@link #checkOpenId(ConsumerToken)} 返回null会走该方法
* 没有账号时处理自己的业务此处必须返回 构造出的登录用户否则会抛出{@link ThirdPartyAuthorizationException 第三方授权异常}
* <br/>
*
* @param consumerToken 登录信息
* @return
*/
@Override
public Object addConsumer(ConsumerToken consumerToken) {
Consumer consumer = new Consumer();
consumer.setConsumerId(StrUtil.genUUID());
consumer.setMobile(StrUtil.isNotEmpty(consumerToken.getUsername()) ? consumerToken.getUsername() : consumerToken.getOpenid());
consumer.setPwd(StrUtil.toMD5("123456"));
consumer.setEnable(true);
consumer.setNonExpire(true);
consumer.setNonLock(true);
List<String> roles = new ArrayList<>();
roles.add("ROLE_CONSUMER");
consumer.setRoles(roles);
switch (consumerToken.getLogtype()) {
case LogTypeConsts.SMS:
ApiVO apiVO = checkSmsCode2Redis(BizConsts.CONSUMER_LOGIN_SMS, consumerToken.getUsername(),
consumerToken.getSmscode());
if (apiVO.isFail()) {
throw new ThirdPartyAuthorizationException("短信验证码错误");
}
consumer.setNickname(consumerToken.getUsername());
consumer.setHead("/head/def.png");
consumer.setRegType(DictRegTypeConsts.DICT_MOBILE);
break;
case LogTypeConsts.QQ:
consumer.setQqId(consumerToken.getOpenid());
consumer.setNickname(consumerToken.getNickname());
setHeader(consumerToken, consumer, false);
setSex(consumerToken, consumer);
consumer.setRegType(DictRegTypeConsts.DICT_QQ);
break;
case LogTypeConsts.WECHAT:
consumer.setWechatId(consumerToken.getOpenid());
consumer.setNickname(consumerToken.getNickname());
setHeader(consumerToken, consumer, false);
setSex(consumerToken, consumer);
consumer.setRegType(DictRegTypeConsts.DICT_WECHAT);
break;
case LogTypeConsts.WEIBO:
consumer.setWeiboId(consumerToken.getOpenid());
consumer.setNickname(consumerToken.getNickname());
setHeader(consumerToken, consumer, false);
setSex(consumerToken, consumer);
consumer.setRegType(DictRegTypeConsts.DICT_WEIBO);
break;
default:
throw new ThirdPartyAuthorizationException("暂不支持该第三方授权");
}
Integer result = consumerMapper.insert(consumer);
if (result < 1) {
/**
* 会抛出{@link ThirdPartyAuthorizationException 第三方授权异常}
*/
return null;
}
return consumer;
}
/**
* 第三方登录 QQ登录
*
@ -173,69 +277,6 @@ public class UserServiceImpl implements UserService {
}
/**
* 没有账号时处理自己的业务此次必须返回 构造出的登录用户否则会抛出{@link ThirdPartyAuthorizationException 第三方授权异常}
* <br/>
* 返回封装登录用户信息到 apiVO.setObject1(User.class) 自己封装登录用户信息
*
* @param consumerToken 登录信息
* @return
*/
@Override
public ApiVO addConsumer(ConsumerToken consumerToken) {
Consumer consumer = new Consumer();
consumer.setConsumerId(StrUtil.genUUID());
consumer.setMobile(StrUtil.isNotEmpty(consumerToken.getUsername()) ? consumerToken.getUsername() : consumerToken.getOpenid());
consumer.setPwd(StrUtil.toMD5("123456"));
consumer.setEnable(true);
consumer.setNonExpire(true);
consumer.setNonLock(true);
List<String> roles = new ArrayList<>();
roles.add("ROLE_CONSUMER");
consumer.setRoles(roles);
switch (consumerToken.getLogtype()) {
case LogTypeConsts.SMS:
ApiVO apiVO = checkSmsCode2Redis(BizConsts.CONSUMER_LOGIN_SMS, consumerToken.getUsername(),
consumerToken.getSmscode());
if (apiVO.isFail()) {
return apiVO;
}
consumer.setNickname(consumerToken.getUsername());
consumer.setHead("/head/def.png");
consumer.setRegType(DictRegTypeConsts.DICT_MOBILE);
break;
case LogTypeConsts.QQ:
consumer.setQqId(consumerToken.getOpenid());
consumer.setNickname(consumerToken.getNickname());
setHeader(consumerToken, consumer, false);
setSex(consumerToken, consumer);
consumer.setRegType(DictRegTypeConsts.DICT_QQ);
break;
case LogTypeConsts.WECHAT:
consumer.setWechatId(consumerToken.getOpenid());
consumer.setNickname(consumerToken.getNickname());
setHeader(consumerToken, consumer, false);
setSex(consumerToken, consumer);
consumer.setRegType(DictRegTypeConsts.DICT_WECHAT);
break;
case LogTypeConsts.WEIBO:
consumer.setWeiboId(consumerToken.getOpenid());
consumer.setNickname(consumerToken.getNickname());
setHeader(consumerToken, consumer, false);
setSex(consumerToken, consumer);
consumer.setRegType(DictRegTypeConsts.DICT_WEIBO);
break;
default:
return new ApiVO(ApiVO.STATUS.F, "暂不支持的登录方式");
}
Integer result = consumerMapper.insert(consumer);
if (result < 1) {
return new ApiVO(ApiVO.STATUS.F, RespsConsts.CODE_FAIL, "登录失败");
}
return new ApiVO(ApiVO.STATUS.S).setObject1(consumer);
}
public Consumer getConsumerByQQOpenid(String openid) {
QueryWrapper queryWrapper = new QueryWrapper<>();
queryWrapper.eq("qq_id", openid);

View File

@ -238,6 +238,7 @@ public class Consumer extends Model<Consumer> implements User {
return this;
}
@Override
public List<String> getRoles() {
return roles;
}

View File

@ -1,7 +1,23 @@
package com.yexuejc.springboot.base.web;
import com.yexuejc.base.http.Resps;
import com.yexuejc.base.pojo.ApiVO;
import com.yexuejc.base.util.RegexUtil;
import com.yexuejc.base.util.StrUtil;
import com.yexuejc.springboot.base.autoconfigure.MutiRedisAutoConfiguration;
import com.yexuejc.springboot.base.constant.BizConsts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* <pre>
* Security 登录注册相关controller
@ -19,5 +35,49 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
public class SecurityCtrl {
@Autowired
@Qualifier(MutiRedisAutoConfiguration.BEAN_REDIS_TEMPLATE1)
RedisTemplate<Object, Object> redisTemplate;
/**
* 登录发送短信
*
* @param mobile
* @return
*/
@RequestMapping(value = "/login/{mobile}", method = RequestMethod.POST)
public Resps login(@PathVariable String mobile) {
if (!RegexUtil.regex(mobile, RegexUtil.REGEX_MOBILE)) {
return Resps.fail("手机号不正确");
}
ApiVO apiVO = sendSmsCode(BizConsts.CONSUMER_LOGIN_SMS, mobile);
if (apiVO.isFail()) {
return Resps.fail(apiVO.getMsg());
}
return Resps.success(apiVO.getMsg());
}
private ApiVO sendSmsCode(String smsType, String mobile) {
String smsId = StrUtil.genUUID(30);
String code = StrUtil.genNum().substring(2, 8);
//自己接入短信发送
boolean result = true;
if (result) {
//成功
//存reids
Map<String, Object> map = new HashMap<>();
map.put("smsId", smsId);
map.put("code", code);
map.put("trade_id", "短信返回id");
map.put("validatedNums", 0);
redisTemplate.afterPropertiesSet();
redisTemplate.opsForHash().putAll(smsType + "." + mobile, map);
// 过期时间5分钟
redisTemplate.expire(smsType + "." + mobile, 5 * 60, TimeUnit.SECONDS);
return new ApiVO(ApiVO.STATUS.S, "短信发送成功");
} else {
return new ApiVO(ApiVO.STATUS.F, "短信发送失败");
}
}
}