1.2.0多方登录稳定版
|
@ -13,9 +13,9 @@ parent:版本封装<br/>
|
|||
base:功能封装
|
||||
|
||||
#### 最新版本
|
||||
* 1.x yexuejc.springboot.version=1.1.4 <br>
|
||||
* 2.x yexuejc.springboot.version=2.0.3 <br>
|
||||
* yexuejc.base.version=1.2.1
|
||||
* 1.x yexuejc.springboot.version=1.2.0 <br>
|
||||
* 2.x yexuejc.springboot.version=2.0.5 <br>
|
||||
* yexuejc.base.version=1.2.4
|
||||
|
||||
pom.xml
|
||||
```
|
||||
|
|
14
UPDATE.md
|
@ -1,6 +1,20 @@
|
|||
yexuejc-springboot 更新内容
|
||||
-------------------
|
||||
|
||||
#### version :1.2.0
|
||||
**time:2018-12-1 12:19:06** <br/>
|
||||
**branch:** master <br/>
|
||||
**关联工程:** <br/>
|
||||
```
|
||||
springboot-base:1.2.4
|
||||
spring-boot-starter-parent:1.5.16.RELEASE
|
||||
```
|
||||
**update:** <br/>
|
||||
1. security多方登录第一个稳定版<br/>
|
||||
支持账号登录、短信登录、第三方授权openid登录<br/>
|
||||
功能链接[security重构-多方登录](doc/SECURITY.md)
|
||||
#
|
||||
|
||||
#### version :1.1.6-1.1.9
|
||||
**time:2018-11-21 15:03:01** <br/>
|
||||
**branch:** master <br/>
|
||||
|
|
|
@ -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/>
|
||||
**获取登录用户信息**
|
||||
|
|
|
@ -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)
|
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 240 KiB |
4
pom.xml
|
@ -5,7 +5,7 @@
|
|||
|
||||
<groupId>com.yexuejc.springboot</groupId>
|
||||
<artifactId>yexuejc-springboot-parent</artifactId>
|
||||
<version>1.1.9</version>
|
||||
<version>1.2.0</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>${project.artifactId}</name>
|
||||
|
@ -20,7 +20,7 @@
|
|||
</parent>
|
||||
|
||||
<properties>
|
||||
<yexuejc.base.version>1.2.2</yexuejc.base.version>
|
||||
<yexuejc.base.version>1.2.4</yexuejc.base.version>
|
||||
<repos.yexuejc.url>https://nexus.yexuejc.club/repository/</repos.yexuejc.url>
|
||||
|
||||
<repos.aliyun.url>http://maven.aliyun.com/nexus/content/groups/public</repos.aliyun.url>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<parent>
|
||||
<groupId>com.yexuejc.springboot</groupId>
|
||||
<artifactId>yexuejc-springboot-parent</artifactId>
|
||||
<version>1.1.9</version>
|
||||
<version>1.2.0</version>
|
||||
<!-- 本地打包:使用相对关联路径 -->
|
||||
<!--<relativePath>../../yexuejc</relativePath>-->
|
||||
</parent>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, "短信发送失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|