Author SHA1 Message Date
winLsy 71ead5c95a 更新 2018-01-23 14:06:26 +08:00
winLsy f66937e5e3 app登录,浏览器登录开发完毕 2018-01-22 22:17:57 +08:00
winLsy 9f2673b424 单点登录实例项目 2018-01-22 22:03:04 +08:00
winLsy 0d0840edd7 解析jwt 2018-01-21 19:43:28 +08:00
winLsy f21a6a156f 使用jwt替换默认的令牌 2018-01-21 19:09:16 +08:00
winLsy 176825f1d3 修改token的存储策略 2018-01-21 17:46:29 +08:00
winLsy 9df28d563f app第三方账户绑定用户功能实现 2018-01-21 16:41:01 +08:00
winLsy 87c4c2a2f5 app使用标准的Oauth2协议获取accessToken 2018-01-21 15:20:25 +08:00
winLsy 22b069a0e9 使用openId换取access_token功能完成 2018-01-21 14:39:19 +08:00
winLsy 5d1071d704 重构验证码的存储模式 2018-01-20 22:59:47 +08:00
winLsy bf6ce3c0ab app登录实现进行中 2018-01-20 18:19:53 +08:00
winLsy 2e46cc6ab7 开始开发app登录 2018-01-19 21:40:10 +08:00
winLsy 95265e6509 退出登录配置 2018-01-18 21:37:23 +08:00
winLsy 9a28f69370 使用redis来存储session配置 2018-01-18 21:01:12 +08:00
winLsy 52b39dde31 添加session管理配置 2018-01-18 20:39:51 +08:00
winLsy e067c8ea2c 优化 2018-01-17 22:17:50 +08:00
winLsy 2d359bf8db 修改体系内用户登录返回的user为SocialUser 2018-01-17 20:46:41 +08:00
winLsy 82a7e8a12a 增加统一服务响应类 2018-01-17 20:16:32 +08:00
winLsy 82be6bbf69 session并发登录处理 2018-01-17 18:52:48 +08:00
winLsy b4cc49db4f 增加session失效处理 2018-01-17 18:28:33 +08:00
winLsy 82eb4884e5 查询第三方绑定,绑定,解绑功能开发 2018-01-17 17:39:03 +08:00
winLsy e917427314 查询当前绑定社交账户开发 2018-01-17 16:30:26 +08:00
winLsy 1caa12ed77 完善微信登录功能 2018-01-17 14:47:36 +08:00
winLsy 71a9415afb 微信登录初步开发完毕 2018-01-17 13:57:41 +08:00
winLsy 9bfde42a6b 完善文档 2018-01-17 13:56:48 +08:00
winLsy 310e629928 开始开发微信登录功能 2018-01-17 09:25:46 +08:00
winLsy 934f90f15d 更新文档 2018-01-17 09:13:47 +08:00
winLsy d0a49efd4a 对第三方登录中的注册逻辑进行处理 2018-01-16 22:17:04 +08:00
winLsy 6b44d9551e qq登录开发初步完成 2018-01-16 19:28:57 +08:00
winLsy 86c3114cb8 更新文档 2018-01-16 17:47:03 +08:00
winLsy 462a4de3a6 自定义第三方登录的过滤器 2018-01-16 17:14:06 +08:00
winLsy c8cff87398 配置第三方登录在浏览器项目中生效 2018-01-16 16:37:02 +08:00
winLsy 63efcfc54a 添加第三方登录配置类,和文档说明 2018-01-16 16:13:18 +08:00
winLsy 4a946ead68 根据用户名获取第三方登录对象 2018-01-16 15:44:43 +08:00
winLsy c98f81f045 创建第三方授权数据表 2018-01-16 15:12:15 +08:00
winLsy 4353188304 修改注释 2018-01-16 14:30:54 +08:00
winLsy 14a9e80128 添加注释 2018-01-16 14:26:17 +08:00
winLsy 7d6ca5b708 Merge branch 'dev' of https://gitee.com/kklt1996/micro-fast into dev 2018-01-16 12:55:27 +08:00
lsy 9ea6b1cd5e 同步 2018-01-15 22:32:34 +08:00
winLsy cc7cb41812 开始第三方登录的开发 2018-01-15 21:44:42 +08:00
lsy 4ab580e14f 更新 2018-01-12 02:32:50 +08:00
lsy 6bb7847da8 更新文档 2018-01-09 21:04:55 +08:00
lsy 8611a50764 增加index.vue组件 2018-01-09 20:47:09 +08:00
lsy a1f9db825d 用户注册提示 2018-01-09 17:07:11 +08:00
lsy 701a6b2274 更改跨域cookie,和创建表sql,等 2018-01-09 17:06:07 +08:00
lsy 7bc79359a5 使用jackson过滤掉空值字段 2018-01-09 15:08:26 +08:00
lsy 63f0a371fd 增加参数精确校验,统一参数绑定异常,将异常通知处理类移入ssm基础项目 2018-01-09 11:45:41 +08:00
lsy 51cc5466be upms用户注册,异常处理功能编写 2018-01-08 22:35:43 +08:00
lsy c5c846abed 抽离全局变量 2018-01-08 14:40:16 +08:00
lsy 583bd04e91 修改页面原始字体 2018-01-08 13:35:27 +08:00
lsy 160c0d6a2b 集成druid-springboot完毕 2017-12-27 23:38:49 +08:00
lsy 639f263e83 集成druid-springboot完毕 2017-12-27 23:36:07 +08:00
131 changed files with 4161 additions and 341 deletions
+11 -10
View File
@@ -22,9 +22,14 @@
<artifactId>boot-starter-ssm</artifactId>
<version>${micro.fast.version}</version>
</dependency>
<dependency>
<groupId>com.micro.fast</groupId>
<artifactId>boot-starter-security-app</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -41,21 +46,17 @@
</plugin>
<!--生成mybatis dao 层 sql mapper的插件-->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<configuration>
<verbose>true</verbose>
<overwrite>false</overwrite>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<!--<plugin>-->
<!--<groupId>org.apache.maven.plugins</groupId>-->
<!--<artifactId>maven-javadoc-plugin</artifactId>-->
<!--</plugin>-->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
@@ -7,9 +7,8 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan(basePackages = {"com.micro.fast.pa.dao"})
public class PerformanceAppraisalApplication {
public class AllInOneApplication {
public static void main(String[] args) {
SpringApplication.run(PerformanceAppraisalApplication.class, args);
SpringApplication.run(AllInOneApplication.class, args);
}
}
@@ -30,8 +30,8 @@ server:
jdbc:
master:
username: root
password: hfAhyoTu3UKAfmy27/RNKQ==
url: jdbc:mysql://127.0.0.1:3317/micro?useUncoide=true&chracterEncoding=utf-8&useSSL=true
password: studyj2e
url: jdbc:mysql://127.0.0.1:3317/micro?useUncoide=true&chracterEncoding=utf-8&useSSL=false
driverClass: com.mysql.jdbc.Driver
mybatis:
typeAliasesPackage: com.lishouyu.pojo
@@ -7,7 +7,7 @@ import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class PerformanceAppraisalApplicationTests {
public class AllInOneApplicationTests {
@Test
public void contextLoads() {
@@ -104,7 +104,7 @@ var activeTableTab = "activeTableTab";
<li>java.lang.Enum&lt;<a href="../../../../../com/micro/fast/common/response/Const.ServerResponseCode.html" title="com.micro.fast.common.response中的枚举">Const.ServerResponseCode</a>&gt;</li>
<li>
<ul class="inheritance">
<li>com.micro.fast.common.response.Const.ServerResponseCode</li>
<li>com.micro.fast.common.response.BaseConst.ServerResponseCode</li>
</ul>
</li>
</ul>
@@ -95,7 +95,7 @@
<li>java.lang.Object</li>
<li>
<ul class="inheritance">
<li>com.micro.fast.common.response.Const</li>
<li>com.micro.fast.common.response.BaseConst</li>
</ul>
</li>
</ul>
@@ -4,7 +4,7 @@
<head>
<!-- Generated by javadoc (1.8.0_144) on Mon Oct 02 18:41:08 CST 2017 -->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>类 com.micro.fast.common.response.Const.ServerResponseCode的使用</title>
<title>类 com.micro.fast.common.response.BaseConst.ServerResponseCode的使用</title>
<meta name="date" content="2017-10-02">
<link rel="stylesheet" type="text/css" href="../../../../../../stylesheet.css" title="Style">
<script type="text/javascript" src="../../../../../../script.js"></script>
@@ -13,7 +13,7 @@
<script type="text/javascript"><!--
try {
if (location.href.indexOf('is-external=true') == -1) {
parent.document.title="\u7C7B com.micro.fast.common.response.Const.ServerResponseCode\u7684\u4F7F\u7528";
parent.document.title="\u7C7B com.micro.fast.common.response.BaseConst.ServerResponseCode\u7684\u4F7F\u7528";
}
}
catch(err) {
@@ -71,7 +71,7 @@
</a></div>
<!-- ========= END OF TOP NAVBAR ========= -->
<div class="header">
<h2 title="类的使用 com.micro.fast.common.response.Const.ServerResponseCode" class="title">类的使用<br>com.micro.fast.common.response.Const.ServerResponseCode</h2>
<h2 title="类的使用 com.micro.fast.common.response.BaseConst.ServerResponseCode" class="title">类的使用<br>com.micro.fast.common.response.BaseConst.ServerResponseCode</h2>
</div>
<div class="classUseContainer">
<ul class="blockList">
@@ -13,7 +13,7 @@
<script type="text/javascript"><!--
try {
if (location.href.indexOf('is-external=true') == -1) {
parent.document.title="\u7C7B com.micro.fast.common.response.Const\u7684\u4F7F\u7528";
parent.document.title="\u7C7B com.micro.fast.common.response.BaseConst\u7684\u4F7F\u7528";
}
}
catch(err) {
@@ -71,7 +71,7 @@
</a></div>
<!-- ========= END OF TOP NAVBAR ========= -->
<div class="header">
<h2 title="类的使用 com.micro.fast.common.response.Const" class="title">类的使用<br>com.micro.fast.common.response.Const</h2>
<h2 title="类的使用 com.micro.fast.common.response.BaseConst" class="title">类的使用<br>com.micro.fast.common.response.BaseConst</h2>
</div>
<div class="classUseContainer">没有com.micro.fast.common.response.Const的用法</div>
<!-- ======= START OF BOTTOM NAVBAR ====== -->
+19
View File
@@ -0,0 +1,19 @@
-- This SQL contains a "create table" that can be used to create a table that JdbcUsersConnectionRepository can persist
-- connection in. It is, however, not to be assumed to be production-ready, all-purpose SQL. It is merely representative
-- of the kind of table that JdbcUsersConnectionRepository works with. The table and column names, as well as the general
-- column types, are what is important. Specific column types and sizes that work may vary across database vendors and
-- the required sizes may vary across API providers.
USE micro;
create table upms_UserConnection (userId varchar(255) not null,
providerId varchar(255) not null,
providerUserId varchar(255),
rank int not null,
displayName varchar(255),
profileUrl varchar(512),
imageUrl varchar(512),
accessToken varchar(512) not null,
secret varchar(512),
refreshToken varchar(512),
expireTime bigint,
primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on upms_UserConnection(userId, providerId, rank);
+1 -1
View File
@@ -49,7 +49,7 @@ DROP TABLE IF EXISTS ucenter_user;
CREATE TABLE ucenter_user (
`id` INT(10) UNSIGNED AUTO_INCREMENT NOT NULL
COMMENT '编号',
`password` VARCHAR(32) NOT NULL
`password` VARCHAR(200) NOT NULL
COMMENT '密码(MD5(密码+salt))',
`salt` VARCHAR(32) DEFAULT NULL
COMMENT '盐值',
+1 -1
View File
@@ -67,7 +67,7 @@ CREATE TABLE upms_user (
COMMENT '用户的编号',
`username` VARCHAR(20) NOT NULL
COMMENT '用户名',
`password` VARCHAR(32) NOT NULL
`password` VARCHAR(200) NOT NULL
COMMENT '密码加密后的md5值(密码+盐值)',
`salt` VARCHAR(32) DEFAULT NULL
COMMENT '盐值',
@@ -0,0 +1,3 @@
- oauth2请求的用户必须有一个ROLE_USER的用户权限
- 相同的用户,会得到相同的token
@@ -0,0 +1,49 @@
package com.micro.fast.security.app;
import com.micro.fast.security.app.social.AppSingUpUtils;
import com.micro.fast.security.core.pojo.SocialUserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
/**
* @author lsy
*/
@RestController
public class AppSecurityController {
@Autowired
private AppSingUpUtils singUpUtils;
@Autowired
private ProviderSignInUtils providerSignInUtils;
/**
* 引导用户注册,关联第三方账户,把用户的信息存储到redis之中
* 需要被授权
* 在注册接口之,需要从redis之中获取数据进行注册
* @param request
* @return
*/
@GetMapping("/social/signUp")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public SocialUserInfo getSocialUserInfo(HttpServletRequest request){
SocialUserInfo socialUserInfo = new SocialUserInfo();
Connection<?> connectionFromSession = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
socialUserInfo.setProviderId(connectionFromSession.getKey().getProviderId());
socialUserInfo.setProviderUserId(connectionFromSession.getKey().getProviderUserId());
socialUserInfo.setHeadingUrl(connectionFromSession.getDisplayName());
socialUserInfo.setHeadingUrl(connectionFromSession.getImageUrl());
singUpUtils.saveConnectionData(new ServletWebRequest(request),connectionFromSession.createData());
return socialUserInfo;
}
}
@@ -0,0 +1,56 @@
package com.micro.fast.security.app;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.exception.ValidateCodeException;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.concurrent.TimeUnit;
/**
* app模块存储验证码
* @author lsy
*/
@Component
public class AppValidateCodeRepository implements ValidateCodeRepository {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Override
public void save(ServletWebRequest request, ValidateCode code, String validateCodeType) {
redisTemplate.opsForValue().set(buildKey(request,validateCodeType),code,30, TimeUnit.MINUTES);
}
@Override
public ValidateCode get(ServletWebRequest request, String validateCodeType) {
Object o = redisTemplate.opsForValue().get(buildKey(request, validateCodeType));
if (o==null){
return null;
}
return (ValidateCode) o;
}
@Override
public void remove(ServletWebRequest request, String validateCodeType) {
redisTemplate.delete(buildKey(request,validateCodeType));
}
/**
* 构建redis的key
* @param request
* @param validateCodeType
* @return
*/
private String buildKey(ServletWebRequest request,String validateCodeType){
String deviceId = request.getHeader("deviceId");
if (StringUtils.isBlank(deviceId)){
throw new ValidateCodeException("请求头中缺少机器的唯一识别");
}
return "code:"+validateCodeType.toLowerCase()+":"+deviceId;
}
}
@@ -1,8 +1,15 @@
package com.micro.fast.security.app;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@SpringBootApplication
/**
* 自动配置入口类
* @author lsy
*/
@Configuration
@ComponentScan
public class BootStarterSecurityAppApplication {
}
@@ -0,0 +1,30 @@
package com.micro.fast.security.app;
import com.micro.fast.security.core.social.MsSpringSocialConfigurer;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
/**
* spring中所有的bean初始化之前和初始化之后都要经过这两个方法
* @author lsy
*/
@Component
public class SpringSocialConfigurePostProcessor implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (StringUtils.equals(beanName,"msSpringSocialConfigurer")){
MsSpringSocialConfigurer configurer = (MsSpringSocialConfigurer)bean;
//app第三方用户的注册
configurer.signupUrl("/social/signUp");
return configurer;
}
return bean;
}
}
@@ -0,0 +1,65 @@
package com.micro.fast.security.app;
import com.micro.fast.security.app.jwt.MsJwtEnhancer;
import com.micro.fast.security.core.master.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
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;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
/**
* 配置令牌的存取策略
* @author lsy
*/
@Configuration
public class TokenStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
@ConditionalOnProperty(prefix = "ms.security.oauth2",name = "storeType",havingValue = "redis",matchIfMissing = false)
public TokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
/**
* 如果存在ms.security.oauth2.storeType的配置存在且值是jwt这个配置就生效
* 如果不存在这个ms.security.oauth2.storeType属性就默认生效
*/
@Configuration
@ConditionalOnProperty(prefix = "ms.security.oauth2",name = "storeType",havingValue = "jwt",matchIfMissing = true)
public static class JwtTokenConfig{
@Autowired
private SecurityProperties securityProperties;
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
//根据秘钥生成token
jwtAccessTokenConverter.setSigningKey(securityProperties.getOauth2().getJwtSigningKey());
return jwtAccessTokenConverter;
}
@Bean
@ConditionalOnMissingBean
public TokenEnhancer tokenEnhancer(){
return new MsJwtEnhancer();
}
}
}
@@ -0,0 +1,98 @@
package com.micro.fast.security.app.config.authorization2;
import com.micro.fast.security.core.master.OAuth2ClientProperties;
import com.micro.fast.security.core.master.SecurityProperties;
import com.sun.org.apache.regexp.internal.RE;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.builders.InMemoryClientDetailsServiceBuilder;
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.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;
/**
* OAuth2协议标准服务商实现配置类,配置token的生成存储策略
* @author lsy
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationProviderConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired(required = true)
private UserDetailsService userDetailsService;
@Autowired
private SecurityProperties securityProperties;
/**
* 只有在jwt环境下才生效
*/
@Autowired(required = false)
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenEnhancer jwtTokenEnhancer;
@Autowired
private TokenStore tokenStore;
/**
* 配置token的存取策略,以及增强策略
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
if (jwtAccessTokenConverter!=null && jwtTokenEnhancer!=null){
//声明token增强链
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
//将增强逻辑填入增强链
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(jwtTokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancers);
//将增强链放入扩展点中
endpoints.tokenEnhancer(enhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
/**
* 配置哪些应用可以获取token
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())){
for (OAuth2ClientProperties client: securityProperties.getOauth2().getClients()){
builder.withClient(client.getClientId()).secret(client.getClientSecret())
.accessTokenValiditySeconds(client.getAccessTokenValiditySeconds())
.authorizedGrantTypes("refresh_token","password")
//refreshToken的过期时间,refreshToken是用来在用户无感知的情况下换取token的
.refreshTokenValiditySeconds(2592000)
.scopes("all","read","write");
}
}
}
}
@@ -0,0 +1,110 @@
package com.micro.fast.security.app.config.authorization2;
import com.micro.fast.security.app.config.component.MsAuthenticationFailureHandler;
import com.micro.fast.security.app.config.component.MsAuthenticationSuccessHandler;
import com.micro.fast.security.app.social.openid.OpenIdAuthenticationSecurityConfig;
import com.micro.fast.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.micro.fast.security.core.master.SecurityConstants;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.filter.ValidateCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.social.security.SpringSocialConfigurer;
/**
* OAuth2协议标准服务提供实现配置类,资源服务器的配置
* @author lsy
*/
@Configuration
@EnableResourceServer
public class AuthorizationResourceConfig extends ResourceServerConfigurerAdapter {
/**
* todo 待优化,不唯一,应从启动参数中获取
*/
public static final String DRUID = "/druid/*";
@Autowired
private MsAuthenticationFailureHandler msAuthenticationFailureHandler;
@Autowired
private MsAuthenticationSuccessHandler msAuthenticationSuccessHandler;
@Autowired
private SecurityProperties securityProperties;
/**
* 短信验证码登录的配置
*/
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private SpringSocialConfigurer springSocialConfigurer;
@Autowired
private ValidateCodeRepository validateCodeRepository;
@Autowired
private OpenIdAuthenticationSecurityConfig openIdAuthenticationSecurityConfig;
@Override
public void configure(HttpSecurity http) throws Exception {
//创建自定义的验证码过滤器,填入身份认证失败处理对象
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(msAuthenticationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.setValidateCodeRepository(validateCodeRepository);
validateCodeFilter.afterPropertiesSet();
http
//添加自定义的验证码处理器,在用户密码校验过滤器的方式获取令牌之前
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
//使用表单登录
.formLogin()
//自定义登录页面
.loginPage(SecurityConstants.LOGIN_PAGE)
//配置spring security登录接口的访问地址,用于在自定义的登录页面提交请求
.loginProcessingUrl(SecurityConstants.FORM_LOGIN_PROCESSOR_URI)
//定义身份认证成功后处理对象
.successHandler(msAuthenticationSuccessHandler)
//定义身份认证失败后处理对象
.failureHandler(msAuthenticationFailureHandler)
.and()
.authorizeRequests()//下面是权限配置
//当访问这个页面的时候不进行身份认证,对用户自定义的登录页也不进行权限认证
.antMatchers(SecurityConstants.MOBILE_LOGIN_PROCESSOR_URI
, SecurityConstants.FORM_LOGIN_PROCESSOR_URI
, SecurityConstants.GET_VALIDATE_CODE_URI
, SecurityConstants.LOGIN_PAGE
, DRUID
, securityProperties.getBrowser().getHtml()
//用户注册或绑定的页面
, securityProperties.getBrowser().getSignUpUrl()
, "处理用户的注册绑定请求的url"
,"/social/signUp"
)
.permitAll()
//任何请求
.anyRequest()
//都需要身份认证
.authenticated()
.and()
//暂时关闭掉跨站请求攻击防护
.csrf().disable()
//应用短信验证码登录的逻辑
.apply(smsCodeAuthenticationSecurityConfig)
.and()
//应用springSocial的配置,在接收到特殊的请求的时候进行社交登录.例如 /auth/qq 其中/auth是默认的前缀,qq是providerId
.apply(springSocialConfigurer)
.and()
//openid
.apply(openIdAuthenticationSecurityConfig);
;
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.app.config.authorization2;
@@ -0,0 +1,54 @@
package com.micro.fast.security.app.config.component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.micro.fast.security.core.browser.constant.LoginType;
import com.micro.fast.security.core.master.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* AuthenticationFailureHandler
* 身份认证失败的后续处理类
* @author lsy
*/
@Component("msAuthenticationFailureHandler")
public class MsAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private static final Logger log = LoggerFactory.getLogger(MsAuthenticationFailureHandler.class);
/**
* object <=> json 处理对象
*/
@Autowired
private ObjectMapper objectMapper;
/**
* 安全框架配置参数对象
*/
@Autowired
private SecurityProperties securityProperties;
@Override
public void onAuthenticationFailure(
HttpServletRequest httpServletRequest
, HttpServletResponse httpServletResponse
, AuthenticationException e)
throws IOException, ServletException {
//根据配置参数判断身份认证失败后的返回类型进行响应
if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
log.info("failure");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e));
}else{
super.onAuthenticationFailure(httpServletRequest,httpServletResponse,e);
}
}
}
@@ -0,0 +1,124 @@
package com.micro.fast.security.app.config.component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.micro.fast.security.core.master.SecurityProperties;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 身份认证成功后的处理类,在登录成功后进行授权token的处理
*
* @author lsy
*/
@Component("msAuthenticationSuccessHandler")
public class MsAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private static final Logger log = LoggerFactory.getLogger(MsAuthenticationSuccessHandler.class);
/**
* 安全框架配置参数对象
*/
@Autowired
private SecurityProperties securityProperties;
/**
* object <=> json 处理对象
*/
@Autowired
private ObjectMapper objectMapper;
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
@Qualifier("defaultAuthorizationServerTokenServices")
private AuthorizationServerTokenServices authorizationServerTokenServices;
/**
* 登录成功后的处理方法
*
* @param request
* @param response
* @param authentication 用户认证的详细信息
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(
HttpServletRequest request
, HttpServletResponse response
, Authentication authentication
) throws IOException, ServletException {
log.info("登录成功,开始授权令牌");
//1.校验clientId和clientSecret是否对应
String header = request.getHeader("Authorization");
if (header == null && !header.startsWith("Basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中client信息不存在");
}
String[] tokens = this.extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if (clientDetails == null){
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:"+clientId);
}else if (!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
throw new UnapprovedClientAuthenticationException("clientSecret不匹配:"+clientSecret);
}
//2.自定义流程创建access_Token
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication auth2Authentication = new OAuth2Authentication(oAuth2Request,authentication);
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(auth2Authentication);
//3.返回accessToken
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(accessToken));
}
/**
* 对header进行解码
* @param header
* @param request
* @return
* @throws IOException
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8");
byte[] decoded;
try {
decoded = Base64.decode(base64Token);
} catch (IllegalArgumentException var7) {
throw new BadCredentialsException("Failed to decode basic authentication token");
}
String token = new String(decoded, this.getCredentialsCharset(request));
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
} else {
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
}
protected String getCredentialsCharset(HttpServletRequest httpRequest) {
return "UTF-8";
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.app.config.component;
@@ -0,0 +1 @@
package com.micro.fast.security.app.config;
@@ -0,0 +1,24 @@
package com.micro.fast.security.app.jwt;
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 java.util.HashMap;
import java.util.Map;
/**
* 对jwt中的信息进行自定义
* @author lsy
*/
public class MsJwtEnhancer implements TokenEnhancer{
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
//添加自定义的信息,在这里面可以填入角色信息
Map<String,Object> info = new HashMap<>();
info.put("project","ms");
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}
@@ -0,0 +1,69 @@
package com.micro.fast.security.app.social;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;
import java.util.concurrent.TimeUnit;
/**
* app第三方登录的工具类
* @author lsy
*/
@Component
public class AppSingUpUtils {
@Autowired
private UsersConnectionRepository usersConnectionRepository;
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
@Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
/**
* 存储第三方账号关联信息
* @param request
* @param connectionData
*/
public void saveConnectionData(WebRequest request, ConnectionData connectionData){
redisTemplate.opsForValue().set(getKey(request),connectionData,10, TimeUnit.MINUTES);
}
/**
* 注册第三方的用户信息
* @param request
* @param userId
*/
public void doPostSignUp(WebRequest request,String userId){
String key = getKey(request);
if (!redisTemplate.hasKey(key)){
throw new RuntimeException("无法找到缓存的第三方用户信息");
}
ConnectionData connectionData = (ConnectionData)redisTemplate.opsForValue().get(getKey(request));
Connection<?> connection = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId()).createConnection(connectionData);
usersConnectionRepository.createConnectionRepository(userId).addConnection(connection);
redisTemplate.delete(key);
}
/**
* 构建redis的key
* @param request
* @return
*/
protected String getKey(WebRequest request){
String deviceId = request.getHeader("deviceId");
if (StringUtils.isBlank(deviceId)){
throw new RuntimeException("机器唯一标识参数不能为空");
}
return "ms:security:social.connect."+deviceId;
}
}
@@ -0,0 +1,24 @@
package com.micro.fast.security.app.social.impl;
import com.micro.fast.security.core.social.SocialAuthenticationFilterPostProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.social.security.SocialAuthenticationFilter;
import org.springframework.stereotype.Component;
/**
* 社交登录成功后的后处理器实现
* @author lsy
*/
@Component
public class AppSocialAuthenticationFilterProcessor implements SocialAuthenticationFilterPostProcessor {
@Autowired
private AuthenticationSuccessHandler successHandler;
@Override
public void process(SocialAuthenticationFilter socialAuthenticationFilter) {
//传入授权成功处理器
socialAuthenticationFilter.setAuthenticationSuccessHandler(successHandler);
}
}
@@ -0,0 +1,98 @@
package com.micro.fast.security.app.social.openid;
import com.micro.fast.security.core.master.SecurityConstants;
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 org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* openId登录身份认证过滤器
* @author lsy
*/
public class OpenIdAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* 从请求中获取openId的key
*/
private String openIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_OPNEID;
/**
* 从请求中获取providerId的key
*/
private String providerIdParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_PROVIDERID;
/**
* 是不是只处理post请求
*/
private boolean postOnly = true;
/**
* 对/authentication/openid路径的post请求进行身份登录操作
*/
public OpenIdAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESS_URL_OPENID, "POST"));
}
/**
* 主要的身份认证方法
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
//获取请求中的mobile
String openid = this.obtainMobile(request);
if (openid == null) {
openid = "";
}
openid = openid.trim();
String providerId = request.getParameter(this.providerIdParameter);
if (providerId == null){
providerId = "";
}
providerId = providerId.trim();
OpenIdAuthenticationToken authRequest = new OpenIdAuthenticationToken(openid,providerId);
this.setDetails(request, authRequest);
//调用AuthenticationManager中的方法
return this.getAuthenticationManager().authenticate(authRequest);
}
}
/**
* 获取openId的方法
* @param request
* @return
*/
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(this.openIdParameter);
}
protected void setDetails(HttpServletRequest request, OpenIdAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setOpenIdParameter(String openIdParameter) {
Assert.hasText(openIdParameter, "Username parameter must not be empty or null");
this.openIdParameter = openIdParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getOpenIdParameter() {
return this.openIdParameter;
}
}
@@ -0,0 +1,75 @@
package com.micro.fast.security.app.social.openid;
import org.apache.commons.collections.CollectionUtils;
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.UserDetails;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.security.SocialUserDetailsService;
import java.util.HashSet;
import java.util.Set;
/**
* 进行openId的身份认证提供类
* @author lsy
*/
public class OpenIdAuthenticationProvider implements AuthenticationProvider {
private SocialUserDetailsService userDetailsService;
private UsersConnectionRepository usersConnectionRepository;
/**
* 返回经过验证的Authentication
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OpenIdAuthenticationToken openIdAuthenticationToken = (OpenIdAuthenticationToken) authentication;
Set<String> providerUserIds = new HashSet<>();
String principal = (String)openIdAuthenticationToken.getPrincipal();
providerUserIds.add(principal);
Set<String> userIds = usersConnectionRepository.findUserIdsConnectedTo(openIdAuthenticationToken.getProviderId(), providerUserIds);
if (CollectionUtils.isEmpty(userIds) || userIds.size()!=1) {
throw new InternalAuthenticationServiceException("无法获取用户的信息");
}
String userId = userIds.iterator().next();
UserDetails socialUserDetails = userDetailsService.loadUserByUserId(userId);
if (socialUserDetails == null){
throw new InternalAuthenticationServiceException("无法获取用户的信息");
}
OpenIdAuthenticationToken openIdAuthenticationTokenResult = new OpenIdAuthenticationToken(socialUserDetails, socialUserDetails.getAuthorities());
openIdAuthenticationTokenResult.setDetails(openIdAuthenticationToken.getDetails());
return openIdAuthenticationTokenResult;
}
/**
* 这个类是不是支持传入的token的校验
* @param authenticate
* @return
*/
@Override
public boolean supports(Class<?> authenticate) {
return OpenIdAuthenticationToken.class.isAssignableFrom(authenticate);
}
public SocialUserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(SocialUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
public UsersConnectionRepository getUsersConnectionRepository() {
return usersConnectionRepository;
}
public void setUsersConnectionRepository(UsersConnectionRepository usersConnectionRepository) {
this.usersConnectionRepository = usersConnectionRepository;
}
}
@@ -0,0 +1,57 @@
package com.micro.fast.security.app.social.openid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.security.SocialUserDetailsService;
import org.springframework.stereotype.Component;
/**
* @author lsy
* openId登录的配置类
*/
@Component
public class OpenIdAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity>{
@Autowired
@Qualifier("msAuthenticationSuccessHandler")
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
@Qualifier("msAuthenticationFailureHandler")
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
@Qualifier("msUserDetailsServiceImpl")
private SocialUserDetailsService userDetailsService ;
@Autowired
private UsersConnectionRepository usersConnectionRepository;
@Override
public void configure(HttpSecurity http) throws Exception {
OpenIdAuthenticationFilter OpenIdAuthenticationFilter = new OpenIdAuthenticationFilter();
//将短信验证码登录过滤器放入AuthenticationManager,方便后续调用
OpenIdAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
OpenIdAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
OpenIdAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
//将根据手机号获取用户信息的对象放入短信验证码的provider中
OpenIdAuthenticationProvider openIdAuthenticationProvider = new OpenIdAuthenticationProvider();
openIdAuthenticationProvider.setUserDetailsService(userDetailsService);
openIdAuthenticationProvider.setUsersConnectionRepository(usersConnectionRepository);
//将自定义的provider放入authenticaionManger管理的集合中去
http.authenticationProvider(openIdAuthenticationProvider)
//将自定义的过滤器添加到用户账号密码校验过滤器后面
.addFilterAfter(OpenIdAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
@@ -0,0 +1,74 @@
package com.micro.fast.security.app.social.openid;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* APP社交登陆中使用openId进行登录的token
* @author lsy
*/
public class OpenIdAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
private final Object principal;
/**
* 服务提供商的id
*/
private String providerId;
/**
* 在没有进行身份认证的时候存储手机号
* @param openId openId
*/
public OpenIdAuthenticationToken(String openId,String providerId) {
super((Collection)null);
this.principal = openId;
this.providerId = providerId;
this.setAuthenticated(false);
}
/**
* @param principal
* @param authorities
*/
public OpenIdAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
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");
} else {
super.setAuthenticated(false);
}
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.app.social;
@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.micro.fast.security.app.BootStarterSecurityAppApplication
@@ -2,9 +2,19 @@
##相关配置参数说明
- `ms.security.browser.html` 自定义登录页面的地址(不一定必须参数)
- `ms.security.browser.signUpUrl` 第三方登录时用户不存在的时候跳转的注册页面或绑定页面 (必要参数)
- `ms.security.browser.loginType` 身份认证后响应类型`REDIRECT`页面形式响应,`JSON`, json形式的响应
- `ms.security.browser.rememberMeSeconds` 记住我的时间,单位是秒,默认的时间是3600秒,`remember-me`参数,值为`true`
- `/authentication/require`处理身份认证跳转的接口地址
- 需要注入`userServiceImpl`获取用户详细信息的bean (必须参数)
- 需要注入数据源`masterDataSource`用于记住我功能的bean,记住我功能需要在登录的时候传入(必须参数)
### 第三方方登录的配置
- 使用这个模块的时候需要借助providerSignInUtils.doPostSignUp();方法实现一个接口将用户的注册或绑定信息插入到数据表中,这里的注
册指的是创建本系统自己的用户体系,第三方的用户体系springSocial会自动帮助我们创建。并将url配置可以直接访问
- 第三方登陆时,当用户不存在的时候,如果想要系统自动注册一个用户,不进行跳转的话。声明一个类似于demo项目中DemoConnectionSignUp
样的组件进行自己用户体系的注册。这里的注册指的是创建本系统自己的用户体系,第三方的用户体系springSocial会自动帮助我们创建。授权后
直接跳转到域名首页
### session管理
- session过期的处理 @GetMapping("/session/invalid")
- 用户并发登录的处理MsExpiredSessionStrategy
@@ -6,4 +6,5 @@ import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class BootStarterSecurityBrowserApplication {
}
@@ -0,0 +1,42 @@
package com.micro.fast.security.browser;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import static com.micro.fast.security.core.validate.code.processor.ValidateCodeProcessor.SESSION_KEY_PREFIX;
/**
* 浏览器存储验证码的策略
* @author lsy
*/
@Component
public class BrowserValidateCodeRepository implements ValidateCodeRepository {
/**
* 操作session的工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
@Override
public void save(ServletWebRequest request, ValidateCode code, String validateCodeType) {
//向session中存储验证码
sessionStrategy.setAttribute(request
,SESSION_KEY_PREFIX+validateCodeType.toUpperCase()
,code);
}
@Override
public ValidateCode get(ServletWebRequest request, String validateCodeType) {
ValidateCode validateCode = (ValidateCode) (sessionStrategy.getAttribute(request, SESSION_KEY_PREFIX + validateCodeType.toUpperCase()));
return null;
}
@Override
public void remove(ServletWebRequest request, String validateCodeType) {
sessionStrategy.removeAttribute(request, SESSION_KEY_PREFIX + validateCodeType.toUpperCase());
}
}
@@ -0,0 +1,39 @@
/**
*
*/
package com.micro.fast.security.browser.config;
import com.micro.fast.security.browser.session.MsExpiredSessionStrategy;
import com.micro.fast.security.browser.session.MsInvalidSessionStrategy;
import com.micro.fast.security.core.master.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
/**
* session过期处理类
* @author lsy
*/
@Configuration
public class BrowserSecurityBeanConfig {
@Autowired
private SecurityProperties securityProperties;
@Bean
@ConditionalOnMissingBean(InvalidSessionStrategy.class)
public InvalidSessionStrategy invalidSessionStrategy(){
return new MsInvalidSessionStrategy(securityProperties.getSession().getSessionInvalidUrl());
}
@Bean
@ConditionalOnMissingBean(SessionInformationExpiredStrategy.class)
public SessionInformationExpiredStrategy sessionInformationExpiredStrategy(){
return new MsExpiredSessionStrategy(securityProperties.getSession().getSessionInvalidUrl());
}
}
@@ -3,6 +3,7 @@ package com.micro.fast.security.browser.config;
import com.micro.fast.security.core.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import com.micro.fast.security.core.master.SecurityConstants;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.filter.ValidateCodeFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -11,15 +12,15 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.social.security.SpringSocialConfigurer;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
@@ -43,6 +44,13 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
//todo 待优化,不唯一,应从启动参数中获取
public static final String DRUID = "/druid/*";
@Autowired
private InvalidSessionStrategy invalidSessionStrategy;
@Autowired
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
/**
* 注入身份认证框架配置参数对象
*/
@@ -71,6 +79,8 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private ValidateCodeRepository validateCodeRepository;
/**
* 短信验证码登录的配置
@@ -78,6 +88,9 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private SpringSocialConfigurer springSocialConfigurer;
/**
* 自定义身份认证配置
*
@@ -90,6 +103,7 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
validateCodeFilter.setAuthenticationFailureHandler(msAuthenticationFailureHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.setValidateCodeRepository(validateCodeRepository);
validateCodeFilter.afterPropertiesSet();
http
//添加自定义的验证码处理器,在用户密码校验过滤器之前
@@ -114,6 +128,24 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
//设置获取用户信息后登录的方式
.userDetailsService(userDetailsService)
.and()
// session管理的配置
.sessionManagement()
//session过期处理的类
.invalidSessionStrategy(invalidSessionStrategy)
//session存在的最大数量
.maximumSessions(securityProperties.getSession().getMaximumSessions())
//设置是否允许第二个用户登录,如果不阻止的话,会替换掉上一个登录用户
.maxSessionsPreventsLogin(securityProperties.getSession().isMaxSessionPreventsLogin())
//设置session被替换掉时处理逻辑,是定义session过期的时候处理的url
.expiredSessionStrategy(sessionInformationExpiredStrategy)
.and()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("自定义退出登录后访问的url") //如果不配置的话就会跳向登录处理url
.deleteCookies("删除浏览器上指定的cookie")
// .logoutSuccessHandler() 定义登出成功处理器,并做一定的处理。这个登出处理器会覆盖掉logoutSuccessUrl的配置
.and()
.authorizeRequests()//下面是权限配置
//当访问这个页面的时候不进行身份认证,对用户自定义的登录页也不进行权限认证
.antMatchers(SecurityConstants.MOBILE_LOGIN_PROCESSOR_URI
@@ -121,7 +153,14 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
, SecurityConstants.GET_VALIDATE_CODE_URI
, SecurityConstants.LOGIN_PAGE
, DRUID
, securityProperties.getBrowser().getHtml())
, securityProperties.getBrowser().getHtml()
//upms系统用户注册
, "/upms/user"
//用户注册或绑定的页面
,securityProperties.getBrowser().getSignUpUrl()
,"处理用户的注册绑定请求的url"
//处理session请求的url
,"/session/invalid")
.permitAll()
//任何请求
.anyRequest()
@@ -131,23 +170,17 @@ public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
//暂时关闭掉跨站请求攻击防护
.csrf().disable()
//应用短信验证码登录的逻辑
.apply(smsCodeAuthenticationSecurityConfig);
.apply(smsCodeAuthenticationSecurityConfig)
.and()
//应用springSocial的配置,在接收到特殊的请求的时候进行社交登录.例如 /auth/qq 其中/auth是默认的前缀,qq是providerId
.apply(springSocialConfigurer);
}
/**
* 定义身份认证红密码的加密解密类的bean
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 用于记住我功能
*
* @return
*/
@Bean
@@ -1,5 +1,6 @@
package com.micro.fast.security.browser.controller;
import com.micro.fast.security.core.pojo.SocialUserInfo;
import com.micro.fast.security.core.master.SecurityConstants;
import com.micro.fast.security.core.master.SecurityProperties;
import org.apache.commons.lang3.StringUtils;
@@ -12,16 +13,23 @@ import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 自定义登录认证处理方式,当触发登录认证的时候跳转到这里
* @author lsy
*/
@RestController
public class BrowserSecurityController {
@@ -29,6 +37,9 @@ public class BrowserSecurityController {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private ProviderSignInUtils providerSignInUtils;
private static final Logger log = LoggerFactory.getLogger(BrowserSecurityController.class);
private static final String REQUEST_URL_SUFFIX = ".html";
@@ -60,5 +71,35 @@ public class BrowserSecurityController {
//如果是异步请求的话就放回404状态码和提示信息
return "未进行身份认证";
}
/**
* 使用providerSignInUtils工具类从session中获取用户第三方登录的信息,这些信息是在第三方
* 登录跳转到注册页面之前的时候放入到session中的
* @param request
* @return
*/
@GetMapping("/social/user")
public SocialUserInfo getSocialUserInfo(HttpServletRequest request){
SocialUserInfo socialUserInfo = new SocialUserInfo();
Connection<?> connectionFromSession = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));
socialUserInfo.setProviderId(connectionFromSession.getKey().getProviderId());
socialUserInfo.setProviderUserId(connectionFromSession.getKey().getProviderUserId());
socialUserInfo.setHeadingUrl(connectionFromSession.getDisplayName());
socialUserInfo.setHeadingUrl(connectionFromSession.getImageUrl());
return socialUserInfo;
}
/**
* 处理session过期
* @return
*/
@GetMapping("/session/invalid")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public Map sessionInvalid(){
HashMap<String, String> result = new HashMap<>();
result.put("msg","登录时间过长,请重新登录");
return result;
}
}
@@ -0,0 +1,97 @@
package com.micro.fast.security.browser.session;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.micro.fast.security.core.reponse.ServerResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author lsy
*/
public class AbstractSessionStrategy {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 跳转的url
*/
private String destinationUrl;
/**
* 重定向策略
*/
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
/**
* 跳转前是否创建新的session
*/
private boolean createNewSession = true;
private ObjectMapper objectMapper = new ObjectMapper();
/**
*
* @param invalidSessionUrl
*/
public AbstractSessionStrategy(String invalidSessionUrl) {
Assert.isTrue(UrlUtils.isValidRedirectUrl(invalidSessionUrl), "url must start with '/' or with 'http(s)'");
this.destinationUrl = invalidSessionUrl;
}
protected void onSessionInvalid(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (createNewSession) {
request.getSession();
}
String sourceUrl = request.getRequestURI();
String targetUrl;
if (StringUtils.endsWithIgnoreCase(sourceUrl, ".html")) {
targetUrl = destinationUrl+".html";
logger.info("session失效,跳转到"+targetUrl);
redirectStrategy.sendRedirect(request, response, targetUrl);
}else{
String message = "session已失效";
if(isConcurrency()){
message = message + ",有可能是并发登录导致的";
}
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(ServerResponse.errorMsg(message)));
}
}
/**
* session失效是否是并发导致的
* @return
*/
protected boolean isConcurrency() {
return false;
}
/**
* Determines whether a new session should be created before redirecting (to
* avoid possible looping issues where the same session ID is sent with the
* redirected request). Alternatively, ensure that the configured URL does
* not pass through the {@code SessionManagementFilter}.
*
* @param createNewSession
* defaults to {@code true}.
*/
public void setCreateNewSession(boolean createNewSession) {
this.createNewSession = createNewSession;
}
}
@@ -0,0 +1,34 @@
/**
*
*/
package com.micro.fast.security.browser.session;
import java.io.IOException;
import javax.servlet.ServletException;
import org.springframework.security.web.session.SessionInformationExpiredEvent;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;
/**
* @author lsy
*/
public class MsExpiredSessionStrategy extends AbstractSessionStrategy implements SessionInformationExpiredStrategy {
public MsExpiredSessionStrategy(String invalidSessionUrl) {
super(invalidSessionUrl);
}
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
onSessionInvalid(event.getRequest(), event.getResponse());
}
@Override
protected boolean isConcurrency() {
return true;
}
}
@@ -0,0 +1,32 @@
/**
*
*/
package com.micro.fast.security.browser.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.stereotype.Component;
/**
*
* @author lsy
*/
@Component
public class MsInvalidSessionStrategy extends AbstractSessionStrategy implements InvalidSessionStrategy {
public MsInvalidSessionStrategy(String invalidSessionUrl) {
super(invalidSessionUrl);
}
@Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
onSessionInvalid(request, response);
}
}
@@ -23,3 +23,25 @@
- 短信登录的接口为`/authentication/mobile`,传递参数为`smsCode`和`mobile`
- `ms.security.createRememberTable` 是否创建记住我表结构默认为`true`
### 本项目提供的第三方登录的配置
qq 第三方登录
- `ms.security.social.qq.appId` qq互联注册的id
- `ms.security.social.qq.appSecret` qq互联注册的密码
- `ms.security.social.qq.providerId` 第三方登录的标识,例如qq为`qq`登陆默认
- `ms.security.social.filterProcessUrl` 进行第三方登录处理的基础url前缀,这个前缀会和providerId进行拼接来进行明确
的第三方登录,这个地址也是从第三方应用携带授权码返回的时候进行处理的地址,如果没有授权码就重定向到第三方登录
weChat 第三方登录
- `ms.security.social.weChat.appId` 类比qq登录
- `ms.security.social.weChat.appSecret` 类比qq登录
- `ms.security.social.weChat.providerId` 类比qq登录
### 绑定,解绑第三方用户
- 查询已绑定的用户,请求`/connect`接口即可,要求当前登录的用户放入session中的是SocialUser的对象,能获取到getId
- 绑定用户就是发起post请求`/connect/providerId`即可.可以使用自定义名称为`qqConnectedView`或`weChatConnectedView`的bean来自定义视图
- 解除绑定的时候,发送delete请求`/connect/providerId`删除社交登录的信息即可。返回的视图和绑定的时候使用相同的视图,在视图里面做逻辑判断
### seesion管理
- `ms.security.session.maximumSessions`同一个系统中的最大session数,默认1
- `ms.security.session.maxSessionPreventsLogin`达到最大session时,是否阻止新的登录请求,默认为false,不阻止,新的登录将老的登
录失效掉
- `ms.security.session.sessionInvalidUrl`session失效的时候跳转的地址
### 退出登录
- 默认的退出登录请求的url是`/logout`,退出登录的时候会删除用户session中的信息和记住我存入数据库中的信息然后默认跳转到登录的url
@@ -1,9 +1,13 @@
package com.micro.fast.security.core;
import com.micro.fast.security.core.master.SecurityProperties;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author lsy
@@ -12,5 +16,14 @@ import org.springframework.context.annotation.Configuration;
@ComponentScan
@EnableConfigurationProperties(SecurityProperties.class)
public class BootStarterSecurityCoreApplication {
/**
* 定义身份认证红密码的加密解密类的bean
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.core.authentication.mobile;
@@ -8,7 +8,10 @@ import com.micro.fast.security.core.browser.constant.LoginType;
* @author lsy
*/
public class BrowserProperties {
/**
* 第三方登录时用户不存在的时候跳转的注册页面
*/
private String signUpUrl = "/ms-signUp.html";
/**
* 用户自自定义的登录页面,不能为空
*/
@@ -47,4 +50,12 @@ public class BrowserProperties {
public void setLoginType(LoginType loginType) {
this.loginType = loginType;
}
public String getSignUpUrl() {
return signUpUrl;
}
public void setSignUpUrl(String signUpUrl) {
this.signUpUrl = signUpUrl;
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.core.browser;
@@ -0,0 +1,56 @@
package com.micro.fast.security.core.master;
/**
* @author lsy
*/
public class OAuth2ClientProperties {
private String clientId;
private String clientSecret;
private int accessTokenValiditySeconds;
private String authorizedGrantTypes;
private String scopes;
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getClientSecret() {
return clientSecret;
}
public void setClientSecret(String clientSecret) {
this.clientSecret = clientSecret;
}
public int getAccessTokenValiditySeconds() {
return accessTokenValiditySeconds;
}
public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {
this.accessTokenValiditySeconds = accessTokenValiditySeconds;
}
public String getAuthorizedGrantTypes() {
return authorizedGrantTypes;
}
public void setAuthorizedGrantTypes(String authorizedGrantTypes) {
this.authorizedGrantTypes = authorizedGrantTypes;
}
public String getScopes() {
return scopes;
}
public void setScopes(String scopes) {
this.scopes = scopes;
}
}
@@ -0,0 +1,30 @@
package com.micro.fast.security.core.master;
/**
* aouth2属性配置
* @author lsy
*/
public class OAuth2Properties {
private OAuth2ClientProperties[] clients = new OAuth2ClientProperties[]{};
private String jwtSigningKey = "msjwt";
private String storeType = "jwt";
public String getJwtSigningKey() {
return jwtSigningKey;
}
public void setJwtSigningKey(String jwtSigningKey) {
this.jwtSigningKey = jwtSigningKey;
}
public OAuth2ClientProperties[] getClients() {
return clients;
}
public void setClients(OAuth2ClientProperties[] clients) {
this.clients = clients;
}
}
@@ -36,4 +36,23 @@ public interface SecurityConstants {
*/
String GET_VALIDATE_CODE_PREFIX = "/code/";
/**
* session失效的处理地址
*/
String DEFAULT_SESSION_INVALID_URL = "/ms-session-invalid.html";
/**
* 获取openId的key
*/
String DEFAULT_PARAMETER_NAME_OPNEID = "openid";
/**
* 获取providerId的key
*/
String DEFAULT_PARAMETER_NAME_PROVIDERID = "providerId";
/**
* openID获取access_token的步骤
*/
String DEFAULT_LOGIN_PROCESS_URL_OPENID = "/authentication/openId";
}
@@ -1,6 +1,7 @@
package com.micro.fast.security.core.master;
import com.micro.fast.security.core.browser.config.param.BrowserProperties;
import com.micro.fast.security.core.social.SocialProperties;
import com.micro.fast.security.core.validate.code.config.param.ValidateCodeProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@@ -25,6 +26,20 @@ public class SecurityProperties {
private Boolean createRememberTable = true;
private SocialProperties social = new SocialProperties();
private SessionProperties session = new SessionProperties();
private OAuth2Properties oauth2 = new OAuth2Properties();
public SessionProperties getSession() {
return session;
}
public void setSession(SessionProperties session) {
this.session = session;
}
public Boolean getCreateRememberTable() {
return createRememberTable;
}
@@ -48,4 +63,20 @@ public class SecurityProperties {
public void setCode(ValidateCodeProperties code) {
this.code = code;
}
public SocialProperties getSocial() {
return social;
}
public void setSocial(SocialProperties social) {
this.social = social;
}
public OAuth2Properties getOauth2() {
return oauth2;
}
public void setOauth2(OAuth2Properties oauth2) {
this.oauth2 = oauth2;
}
}
@@ -0,0 +1,47 @@
package com.micro.fast.security.core.master;
/**
* session处理的配置
* @author lsy
*/
public class SessionProperties {
/**
* 同一个系统中的最大session数,默认1
*/
private int maximumSessions = 1;
/**
* 达到最大session时,是否阻止新的登录请求,默认为false,不阻止,新的登录将老的登录
* 失效掉
*/
private boolean maxSessionPreventsLogin = false;
/**
* session失效的时候跳转的地址
*/
private String sessionInvalidUrl = SecurityConstants.DEFAULT_SESSION_INVALID_URL;
public int getMaximumSessions() {
return maximumSessions;
}
public void setMaximumSessions(int maximumSessions) {
this.maximumSessions = maximumSessions;
}
public boolean isMaxSessionPreventsLogin() {
return maxSessionPreventsLogin;
}
public void setMaxSessionPreventsLogin(boolean maxSessionPreventsLogin) {
this.maxSessionPreventsLogin = maxSessionPreventsLogin;
}
public String getSessionInvalidUrl() {
return sessionInvalidUrl;
}
public void setSessionInvalidUrl(String sessionInvalidUrl) {
this.sessionInvalidUrl = sessionInvalidUrl;
}
}
@@ -0,0 +1,56 @@
package com.micro.fast.security.core.pojo;
/**
* 第三方登录时用户的信息
* @author lsy
*/
public class SocialUserInfo {
/**
* 第三方登录的服务商标识
*/
private String providerId;
/**
* 用户的第三方id
*/
private String providerUserId;
/**
* 昵称
*/
private String nickname;
/**
* 用户头像的url
*/
private String headingUrl;
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
public String getProviderUserId() {
return providerUserId;
}
public void setProviderUserId(String providerUserId) {
this.providerUserId = providerUserId;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getHeadingUrl() {
return headingUrl;
}
public void setHeadingUrl(String headingUrl) {
this.headingUrl = headingUrl;
}
}
@@ -0,0 +1,133 @@
package com.micro.fast.security.core.reponse;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* 统一服务响应类,方法返回值
* 序列化中不包含为空值的字段
* 0表示成功,1表示失败
* @author lishouyu 18332763730@163.com 2017/10/02
* @version 1.0
* @since 1.0
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class ServerResponse<T> {
/**
* 获取数据返回的提示数据字典码
*/
private Integer code;
/**
* 获取数据成功或者失败的时候提示的消息
*/
private String msg;
/**
* 返回的数据
*/
T data;
/**
* 所有的构造方法均为私有,不允许外部直接调用构造方法
*/
public ServerResponse() {
}
private ServerResponse(Integer code) {
this(code, null, null);
}
private ServerResponse(Integer code, String msg) {
this(code, msg, null);
}
/**
* 为了避免T 为string的时候调用的是 上面的构造方法,需要对构造方法进行包装。构造方法设置为私有,不允许外部调用
*
* @param code  响应代码
* @param data  相应数据
*/
private ServerResponse(Integer code, T data) {
this(code, null, data);
}
private ServerResponse(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
/**
* 返回代码是小于等于0的时候表示请求数据失败,或者进行验证时失败
*
* @return 是否成功
*/
@JsonIgnore //使其不再json序列化结果之中
public boolean isSuccess() {
return 0 == this.code;
}
//请求成功
public static <T> ServerResponse<T> success() {
return new ServerResponse<T>(0);
}
public static <T> ServerResponse<T> successMsg(String msg) {
return new ServerResponse<T>(
0,
msg);
}
public static <T> ServerResponse<T> successData(T data) {
return new ServerResponse<T>(0,
data);
}
public static <T> ServerResponse<T> successMsgData(String msg, T data) {
return new ServerResponse<T>(0,
msg, data);
}
//请求失败
public static <T> ServerResponse<T> error() {
return new ServerResponse<T>(1);
}
public static <T> ServerResponse<T> errorMsg(String msg) {
return new ServerResponse<T>(1, msg);
}
public static <T> ServerResponse<T> errorCodeMsg(Integer code, String msg) {
return new ServerResponse<T>(code, msg);
}
}
@@ -0,0 +1,19 @@
package com.micro.fast.security.core.social;
import org.springframework.web.servlet.view.AbstractView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 拦截请求/connect/providerId返回的/connect/providerId视图的信息,这时候是从微信的页面返回来的。所以要给前台一个页面
* @author lsy
*/
public class MsConnectedView extends AbstractView{
@Override
protected void renderMergedOutputModel(Map<String, Object> map, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
httpServletResponse.setContentType("text/html;charset=UTF-8");
httpServletResponse.getWriter().write("<h3>绑定成功</h3>");
}
}
@@ -0,0 +1,39 @@
package com.micro.fast.security.core.social;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.social.connect.Connection;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.AbstractView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 定义一个返回给前端第三方账户绑定情况的视图,拦截请求/connect返回的/connect/status视图的信息,并装换成json格式
* @author lsy
*/
@Component("connect/status")
public class MsConnectionStatusView extends AbstractView {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void renderMergedOutputModel(Map<String, Object> map
, HttpServletRequest httpServletRequest
, HttpServletResponse httpServletResponse)
throws Exception {
Map<String,List<Connection<?>>> connections = (Map<String,List<Connection<?>>>)map.get("connectionMap");
Map<String,Boolean> result = new HashMap<>(5);
connections.forEach((key,value)->{
result.put(key, CollectionUtils.isNotEmpty(value));
});
//返回json格式的数据
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(result));
}
}
@@ -0,0 +1,50 @@
package com.micro.fast.security.core.social;
import org.springframework.social.security.SocialAuthenticationFilter;
import org.springframework.social.security.SpringSocialConfigurer;
/**
* 自定义springSocial配置类,这个bean在SocialConfig中声明
* @author lsy
*/
public class MsSpringSocialConfigurer extends SpringSocialConfigurer{
/**
* 验证登录后处理器
*/
private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;
/**
* 第三方登录的url
*/
private String filterProcessUrl;
public MsSpringSocialConfigurer(String filterProcessUrl) {
this.filterProcessUrl = filterProcessUrl;
}
/**
* 对第三方登录的拦截器进行自定义设置
* @param object
* @param <T>
* @return
*/
@Override
protected <T> T postProcess(T object) {
SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
//设置第三方登录的url
filter.setFilterProcessesUrl(this.filterProcessUrl);
//如果后处理器不为空,我们就把filter传递给自定义的后处理器
if (socialAuthenticationFilterPostProcessor != null){
socialAuthenticationFilterPostProcessor.process(filter);
}
return (T) filter;
}
public SocialAuthenticationFilterPostProcessor getSocialAuthenticationFilterPostProcessor() {
return socialAuthenticationFilterPostProcessor;
}
public void setSocialAuthenticationFilterPostProcessor(SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor) {
this.socialAuthenticationFilterPostProcessor = socialAuthenticationFilterPostProcessor;
}
}
@@ -0,0 +1,10 @@
package com.micro.fast.security.core.social;
import org.springframework.social.security.SocialAuthenticationFilter;
/**
* @author lsy
*/
public interface SocialAuthenticationFilterPostProcessor {
void process(SocialAuthenticationFilter socialAuthenticationFilter);
}
@@ -0,0 +1,86 @@
package com.micro.fast.security.core.social;
import com.micro.fast.security.core.master.SecurityProperties;
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.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.security.SpringSocialConfigurer;
import javax.sql.DataSource;
/**
* 第三方登录的配置类
*/
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter{
/**
* 使用的数据源
*/
@Autowired
@Qualifier("dynamicDataSource")
private DataSource dataSource;
@Autowired
private SecurityProperties securityProperties;
@Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
@Autowired(required = false)
private ConnectionSignUp connectionSignUp;
@Autowired(required = false)
private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;
/**
*
* @param connectionFactoryLocator
* @return
*/
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
//第三参数是加解密的策略,这里指定是不做加解密操作。对插入到数据库中的信息进行加解密
JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
//使用OAuth2.sql创建如果数据表自定义前缀的话在这里声明
jdbcUsersConnectionRepository.setTablePrefix("upms_");
//如果用户定义了connectionSignUp注册用户的逻辑就放入数据库操作类中默认创建用户
if (connectionSignUp !=null){
jdbcUsersConnectionRepository.setConnectionSignUp(connectionSignUp);
}
return jdbcUsersConnectionRepository;
}
/**
* springSocialconfigure的配置的bean
* @return
*/
@Bean("msSpringSocialConfigurer")
public SpringSocialConfigurer springSocialConfigurer(){
SocialProperties social = securityProperties.getSocial();
MsSpringSocialConfigurer msSpringSocialConfigurer = new MsSpringSocialConfigurer(social.getFilterProcessUrl());
// 设置第三方登录用户不存在的时候,注册或绑定的页面。如果设置为空则不跳转
msSpringSocialConfigurer.signupUrl(securityProperties.getBrowser().getSignUpUrl());
//注入后处理器
msSpringSocialConfigurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);
return msSpringSocialConfigurer;
}
/**
* 在第三方登录跳转到的注册页面显示第三方的用户信息
* @return
*/
@Bean
public ProviderSignInUtils providerSignInUtils(){
return new ProviderSignInUtils(connectionFactoryLocator,getUsersConnectionRepository(connectionFactoryLocator));
}
}
@@ -0,0 +1,49 @@
package com.micro.fast.security.core.social;
import com.micro.fast.security.core.social.qq.QQSocialProperties;
import com.micro.fast.security.core.social.wechat.WeChatSocialProperties;
/**
* 第三方登录配置类
* @author lsy
*/
public class SocialProperties {
/**
* QQ登录配置
*/
private QQSocialProperties qq = new QQSocialProperties();
/**
* 微信登录配置
*/
private WeChatSocialProperties weChat = new WeChatSocialProperties();
/**
* 第三方登录拦截url的配置
*/
private String filterProcessUrl = "/auth";
public WeChatSocialProperties getWeChat() {
return weChat;
}
public void setWeChat(WeChatSocialProperties weChat) {
this.weChat = weChat;
}
public QQSocialProperties getQq() {
return qq;
}
public void setQq(QQSocialProperties qq) {
this.qq = qq;
}
public String getFilterProcessUrl() {
return filterProcessUrl;
}
public void setFilterProcessUrl(String filterProcessUrl) {
this.filterProcessUrl = filterProcessUrl;
}
}
@@ -0,0 +1,27 @@
package com.micro.fast.security.core.social.config;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.social.qq.QQSocialProperties;
import com.micro.fast.security.core.social.qq.connet.QQConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.connect.ConnectionFactory;
/**
* @author lsy
*/
@Configuration
@ConditionalOnProperty(prefix = "ms.security.social.qq",name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
QQSocialProperties qq = securityProperties.getSocial().getQq();
return new QQConnectionFactory(qq.getProviderId(),qq.getAppId(),qq.getAppSecret());
}
}
@@ -0,0 +1,22 @@
package com.micro.fast.security.core.social.qq;
import org.springframework.boot.autoconfigure.social.SocialProperties;
/**
* QQ第三方登录配置对象
* @author lsy
*/
public class QQSocialProperties extends SocialProperties{
/**
* 第三方登录标识
*/
private String providerId = "qq";
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
}
@@ -0,0 +1,13 @@
package com.micro.fast.security.core.social.qq.api;
/**
*
* @author lsy
*/
public interface QQ {
/**
* 获取qq用户的信息
* @return
*/
QQUserInfo getQQUserInfo() ;
}
@@ -0,0 +1,161 @@
package com.micro.fast.security.core.social.qq.api;
/**
* QQ用户信息的实体类
* @author lsy
*/
public class QQUserInfo {
/**
* ret : 0
* msg :
* nickname : Peter
* figureurl : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/30
* figureurl_1 : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/50
* figureurl_2 : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/100
* figureurl_qq_1 : http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/40
* figureurl_qq_2 : http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/100
* gender : 男
* is_yellow_vip : 1
* vip : 1
* yellow_vip_level : 7
* level : 7
* is_yellow_year_vip : 1
*/
private int ret;
private String openId;
private String msg;
private String nickname;
private String figureurl;
private String figureurl_1;
private String figureurl_2;
private String figureurl_qq_1;
private String figureurl_qq_2;
private String gender;
private String is_yellow_vip;
private String vip;
private String yellow_vip_level;
private String level;
private String is_yellow_year_vip;
public int getRet() {
return ret;
}
public void setRet(int ret) {
this.ret = ret;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getFigureurl() {
return figureurl;
}
public void setFigureurl(String figureurl) {
this.figureurl = figureurl;
}
public String getFigureurl_1() {
return figureurl_1;
}
public void setFigureurl_1(String figureurl_1) {
this.figureurl_1 = figureurl_1;
}
public String getFigureurl_2() {
return figureurl_2;
}
public void setFigureurl_2(String figureurl_2) {
this.figureurl_2 = figureurl_2;
}
public String getFigureurl_qq_1() {
return figureurl_qq_1;
}
public void setFigureurl_qq_1(String figureurl_qq_1) {
this.figureurl_qq_1 = figureurl_qq_1;
}
public String getFigureurl_qq_2() {
return figureurl_qq_2;
}
public void setFigureurl_qq_2(String figureurl_qq_2) {
this.figureurl_qq_2 = figureurl_qq_2;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getIs_yellow_vip() {
return is_yellow_vip;
}
public void setIs_yellow_vip(String is_yellow_vip) {
this.is_yellow_vip = is_yellow_vip;
}
public String getVip() {
return vip;
}
public void setVip(String vip) {
this.vip = vip;
}
public String getYellow_vip_level() {
return yellow_vip_level;
}
public void setYellow_vip_level(String yellow_vip_level) {
this.yellow_vip_level = yellow_vip_level;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getIs_yellow_year_vip() {
return is_yellow_year_vip;
}
public String getOpenId() {
return openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}
public void setIs_yellow_year_vip(String is_yellow_year_vip) {
this.is_yellow_year_vip = is_yellow_year_vip;
}
}
@@ -0,0 +1,66 @@
package com.micro.fast.security.core.social.qq.api.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.micro.fast.security.core.social.qq.api.QQUserInfo;
import com.micro.fast.security.core.social.qq.api.QQ;
import org.apache.commons.lang3.StringUtils;
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
import org.springframework.social.oauth2.TokenStrategy;
import java.io.IOException;
/**
* 获取qq用户信息的实现类,qqApi用于组装QQServiceProvider
* @author lsy
*/
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
/**
* 获取用户openId的字符串
*/
public static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
/**
* 获取qq用户信息的字符串
*/
public static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
/**
* 系统在qq互联注册的id
*/
private String appId;
/**
* qq用户的id
*/
private String openId;
private ObjectMapper objectMapper = new ObjectMapper();
public QQImpl(String accessToken,String appId){
//将accessToken放在请求的url中
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId = appId;
//使用accessToken获取用户的openId
String url = String.format(URL_GET_OPENID,accessToken);
String result = getRestTemplate().getForObject(url, String.class);
System.out.println(result);
this.openId = StringUtils.substringBetween(result,"\"openid\":\"","\"}");
}
@Override
public QQUserInfo getQQUserInfo() {
//获取qq用户的信息对象
String url = String.format(URL_GET_USERINFO, appId, openId);
String result = getRestTemplate().getForObject(url, String.class);
QQUserInfo qqUserInfo = null;
try {
qqUserInfo = objectMapper.readValue(result, QQUserInfo.class);
//将获取的openId设置在用户里面
qqUserInfo.setOpenId(openId);
} catch (IOException e) {
throw new RuntimeException("获取用户信息失败");
}
return qqUserInfo;
}
}
@@ -0,0 +1,41 @@
package com.micro.fast.security.core.social.qq.config;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.social.MsConnectedView;
import com.micro.fast.security.core.social.qq.QQSocialProperties;
import com.micro.fast.security.core.social.qq.connet.QQConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.connect.ConnectionFactory;
import org.springframework.web.servlet.View;
/**
* @author lsy
*/
@Configuration
@ConditionalOnProperty(prefix = "ms.security.social.qq",name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
QQSocialProperties qq = securityProperties.getSocial().getQq();
return new QQConnectionFactory(qq.getProviderId(),qq.getAppId(),qq.getAppSecret());
}
/**
* 定义绑定成功返回的视图,和返回失败时候的视图
*/
@Bean({"connect/qqConnected","connect/qqConnect"})
@ConditionalOnMissingBean(name = "qqConnectedView")
public View qqConnectedView(){
MsConnectedView msConnectedView = new MsConnectedView();
return msConnectedView;
}
}
@@ -0,0 +1,64 @@
package com.micro.fast.security.core.social.qq.connet;
import com.micro.fast.security.core.social.qq.api.QQ;
import com.micro.fast.security.core.social.qq.api.QQUserInfo;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;
/**
* QQ的ApiAdapter ,用于组装QQConnectionFactory
* @author lsy
*/
public class QQAdapter implements ApiAdapter<QQ> {
/**
* 测试当前的qqApi是否可用
* @param qq
* @return
*/
@Override
public boolean test(QQ qq) {
//默认qq第三方登录永久可用
return true;
}
/**
* 设置用户的连接信息
* @param qq
* @param connectionValues
*/
@Override
public void setConnectionValues(QQ qq, ConnectionValues connectionValues) {
QQUserInfo qqUserInfo = qq.getQQUserInfo();
//设置用户的昵称
connectionValues.setDisplayName(qqUserInfo.getNickname());
//qq用户40*40的头像
connectionValues.setImageUrl(qqUserInfo.getFigureurl_qq_1());
//设置用户的主页
connectionValues.setProfileUrl(null);
//服务商提供的用户的openID
connectionValues.setProviderUserId(qqUserInfo.getOpenId());
}
/**
* QQ没有此功能
* @param qq
* @return
*/
@Override
public UserProfile fetchUserProfile(QQ qq) {
return null;
}
/**
* qq没有此功能
* @param qq
* @param s
*/
@Override
public void updateStatus(QQ qq, String s) {
//do nothing
}
}
@@ -0,0 +1,21 @@
package com.micro.fast.security.core.social.qq.connet;
import com.micro.fast.security.core.social.qq.api.QQ;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
/**
* QQ授权连接工厂对象
* @author lsy
*/
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
/**
* 使用连接工厂创建连接
* @param providerId 服务提供商的唯一标识
* @param appId 应用系统登录第三方授权的用户名
* @param appSecret 应用系统登录第三方授权的密码
*/
public QQConnectionFactory(String providerId
,String appId,String appSecret) {
super(providerId,new QQServiceProvider(appId,appSecret),new QQAdapter());
}
}
@@ -0,0 +1,55 @@
package com.micro.fast.security.core.social.qq.connet;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Template;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.Charset;
import java.util.List;
/**
* 处理qq第三方登录返回授权码Template,默认的OAuth2Template创建的template不能处理text/html的response
* 这个类在QQServiceProvider类中被实例化
* @author lsy
*/
public class QQOAuth2Template extends OAuth2Template{
public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
//默认useParametersForClientAuthentication属性是false的,为false的话不会携带应用的appId和app_secret
setUseParametersForClientAuthentication(true);
}
/**
* 对获取access_Token的过程进行自定义
* @param accessTokenUrl
* @param parameters
* @return
*/
@Override
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
String responseStr = getRestTemplate().postForObject(accessTokenUrl,parameters,String.class);
String[] split = responseStr.split("&");
String accessToken = StringUtils.substringAfterLast(split[0],"=");
String expireIn = StringUtils.substringAfterLast(split[1],"=");
String refreshToken = StringUtils.substringAfterLast(split[2],"=");
return new AccessGrant(accessToken,null,refreshToken,Long.valueOf(expireIn));
}
/**
* 对qq返回的授权码进行处理
* @return
*/
@Override
protected RestTemplate createRestTemplate() {
RestTemplate restTemplate = super.createRestTemplate();
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
//添加处理text/html的转换器
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
}
@@ -0,0 +1,49 @@
package com.micro.fast.security.core.social.qq.connet;
import com.micro.fast.security.core.social.qq.api.QQ;
import com.micro.fast.security.core.social.qq.api.impl.QQImpl;
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
import org.springframework.social.oauth2.OAuth2Template;
/**
* 获取QQ用户信息服务的提供方类,用于组装QQConnectionFactory
* @author lsy
*/
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
/**
* qq分配给应用的id
*/
private String appId;
/**
* 认证服务器地址
*/
public static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
/**
* accessToken的获取地址
*/
public static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";
/**
* qq互联会给每一个应用分配一个appId和一个appSecret,相当于appd的id和密码
* @param appId 应用的id
* @param appSecret 应用的密码
*/
public QQServiceProvider(String appId,String appSecret) {
//使用自定义的OAuthTemplate
super(new QQOAuth2Template(appId,appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN));
this.appId = appId;
}
/**
* 返回获取用户信息的对象
* @param accessToken 会由抽象类传入
* @return
*/
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken ,this.appId);
}
}
@@ -0,0 +1,23 @@
package com.micro.fast.security.core.social.wechat;
import org.springframework.boot.autoconfigure.social.SocialProperties;
/**
* weChat第三方登录配置类
* @author lsy
*/
public class WeChatSocialProperties extends SocialProperties {
/**
* 第三方登录标识
*/
private String providerId = "weChat";
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
}
@@ -0,0 +1,13 @@
package com.micro.fast.security.core.social.wechat.api;
/**
* weChat 获取用户信息的接口
* @author lsy
*/
public interface WeChat {
/**
* 获取微信登录用户的信息
* @return
*/
WeChatUserInfo getWeChatUserInfo(String openId);
}
@@ -0,0 +1,170 @@
package com.micro.fast.security.core.social.wechat.api;
/**
* 微信登录用户的第三方信息
* @author lsy
*/
public class WeChatUserInfo {
/**
* 普通用户的标识,对当前开发者帐号唯一
*/
private String openid;
/**
* 普通用户昵称
*/
private String nickname;
/**
* 语言
*/
private String language;
/**
* 普通用户性别,1为男性,2为女性
*/
private String sex;
/**
* 普通用户个人资料填写的省份
*/
private String province;
/**
* 普通用户个人资料填写的城市
*/
private String city;
/**
* 国家,如中国为CN
*/
private String country;
/**
* 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
*/
private String headimgurl;
/**
* 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
*/
private String[] privilege;
/**
* 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
*/
private String unionid;
/**
* @return the openid
*/
public String getOpenid() {
return openid;
}
/**
* @param openid the openid to set
*/
public void setOpenid(String openid) {
this.openid = openid;
}
/**
* @return the nickname
*/
public String getNickname() {
return nickname;
}
/**
* @param nickname the nickname to set
*/
public void setNickname(String nickname) {
this.nickname = nickname;
}
/**
* @return the sex
*/
public String getSex() {
return sex;
}
/**
* @param sex the sex to set
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* @return the province
*/
public String getProvince() {
return province;
}
/**
* @param province the province to set
*/
public void setProvince(String province) {
this.province = province;
}
/**
* @return the city
*/
public String getCity() {
return city;
}
/**
* @param city the city to set
*/
public void setCity(String city) {
this.city = city;
}
/**
* @return the country
*/
public String getCountry() {
return country;
}
/**
* @param country the country to set
*/
public void setCountry(String country) {
this.country = country;
}
/**
* @return the headimgurl
*/
public String getHeadimgurl() {
return headimgurl;
}
/**
* @param headimgurl the headimgurl to set
*/
public void setHeadimgurl(String headimgurl) {
this.headimgurl = headimgurl;
}
/**
* @return the privilege
*/
public String[] getPrivilege() {
return privilege;
}
/**
* @param privilege the privilege to set
*/
public void setPrivilege(String[] privilege) {
this.privilege = privilege;
}
/**
* @return the unionid
*/
public String getUnionid() {
return unionid;
}
/**
* @param unionid the unionid to set
*/
public void setUnionid(String unionid) {
this.unionid = unionid;
}
/**
* @return the language
*/
public String getLanguage() {
return language;
}
/**
* @param language the language to set
*/
public void setLanguage(String language) {
this.language = language;
}
}
@@ -0,0 +1,61 @@
package com.micro.fast.security.core.social.wechat.api.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.micro.fast.security.core.social.wechat.api.WeChat;
import com.micro.fast.security.core.social.wechat.api.WeChatUserInfo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
import org.springframework.social.oauth2.TokenStrategy;
import java.nio.charset.Charset;
import java.util.List;
/**
* weChat获取用户信息接口的实现类,微信实现的并不是标准的OAuth2协议
* @author lsy
*/
public class WeChatImpl extends AbstractOAuth2ApiBinding implements WeChat {
/**
* 微信接口获取用户信息的url
*/
private static final String URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid=";
private ObjectMapper objectMapper = new ObjectMapper();
/**
* @param accessToken
*/
public WeChatImpl(String accessToken) {
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
}
/**
* 默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。
*/
@Override
protected List<HttpMessageConverter<?>> getMessageConverters() {
List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
messageConverters.remove(0);
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return messageConverters;
}
@Override
public WeChatUserInfo getWeChatUserInfo(String openId) {
String url = URL_GET_USER_INFO + openId;
String response = getRestTemplate().getForObject(url, String.class);
if(StringUtils.contains(response, "errcode")) {
return null;
}
WeChatUserInfo profile = null;
try {
profile = objectMapper.readValue(response, WeChatUserInfo.class);
} catch (Exception e) {
throw new RuntimeException("获取用户信息失败");
}
return profile;
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.core.social.wechat.api.impl;
@@ -0,0 +1 @@
package com.micro.fast.security.core.social.wechat.api;
@@ -0,0 +1,46 @@
/**
*
*/
package com.micro.fast.security.core.social.wechat.config;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.social.MsConnectedView;
import com.micro.fast.security.core.social.wechat.WeChatSocialProperties;
import com.micro.fast.security.core.social.wechat.connet.WeChatConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.connect.ConnectionFactory;
import org.springframework.web.servlet.View;
/**
* 微信登录配置
* @author lsy
*/
@Configuration
@ConditionalOnProperty(prefix = "ms.security.social.weChat", name = "app-id")
public class WeChatAutoConfiguration extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
WeChatSocialProperties weChatConfig = securityProperties.getSocial().getWeChat();
return new WeChatConnectionFactory(weChatConfig.getProviderId(), weChatConfig.getAppId(),
weChatConfig.getAppSecret());
}
/**
* 定义绑定成功返回的视图,和返回失败时候的视图
*/
@Bean({"connect/weChatConnected","connect/weChatConnect"})
@ConditionalOnMissingBean(name = "weChatConnectedView")
public View weChatConnectedView(){
MsConnectedView msConnectedView = new MsConnectedView();
return msConnectedView;
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.core.social.wechat.config;
@@ -0,0 +1,40 @@
/**
*
*/
package com.micro.fast.security.core.social.wechat.connet;
import org.springframework.social.oauth2.AccessGrant;
/**
* 微信的access_token信息。与标准OAuth2协议不同,微信在获取access_token时会同时返回openId,并没有单独的通过accessToke换取openId的服务
* 所以在这里继承了标准AccessGrant,添加了openId字段,作为对微信access_token信息的封装。
* @author lsy
*/
public class WeChatAccessGrant extends AccessGrant {
private String openId;
public WeChatAccessGrant() {
super("");
}
public WeChatAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {
super(accessToken, scope, refreshToken, expiresIn);
}
/**
* @return the openId
*/
public String getOpenId() {
return openId;
}
/**
* @param openId the openId to set
*/
public void setOpenId(String openId) {
this.openId = openId;
}
}
@@ -0,0 +1,65 @@
/**
*
*/
package com.micro.fast.security.core.social.wechat.connet;
import com.micro.fast.security.core.social.wechat.api.WeChat;
import com.micro.fast.security.core.social.wechat.api.WeChatUserInfo;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;
/**
* 微信 api适配器,将微信 api的数据模型转为spring social的标准模型。
* @author lsy
*/
public class WeChatAdapter implements ApiAdapter<WeChat> {
private String openId;
public WeChatAdapter() {}
public WeChatAdapter(String openId){
this.openId = openId;
}
/**
* @param api
* @return
*/
@Override
public boolean test(WeChat api) {
return true;
}
/**
* @param api
* @param values
*/
@Override
public void setConnectionValues(WeChat api, ConnectionValues values) {
WeChatUserInfo profile = api.getWeChatUserInfo(openId);
values.setProviderUserId(profile.getOpenid());
values.setDisplayName(profile.getNickname());
values.setImageUrl(profile.getHeadimgurl());
}
/**
* @param api
* @return
*/
@Override
public UserProfile fetchUserProfile(WeChat api) {
return null;
}
/**
* @param api
* @param message
*/
@Override
public void updateStatus(WeChat api, String message) {
//do nothing
}
}
@@ -0,0 +1,71 @@
/**
*
*/
package com.micro.fast.security.core.social.wechat.connet;
import com.micro.fast.security.core.social.wechat.api.WeChat;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.support.OAuth2Connection;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2ServiceProvider;
/**
* 微信连接工厂
* @author lsy
*/
public class WeChatConnectionFactory extends OAuth2ConnectionFactory<WeChat> {
/**
* @param appId
* @param appSecret
*/
public WeChatConnectionFactory(String providerId, String appId, String appSecret) {
super(providerId, new WeChatServiceProvider(appId, appSecret), new WeChatAdapter());
}
/**
* 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
*/
@Override
protected String extractProviderUserId(AccessGrant accessGrant) {
//如果是微信登录就直接返回 openId
if(accessGrant instanceof WeChatAccessGrant) {
return ((WeChatAccessGrant)accessGrant).getOpenId();
}
return null;
}
/**
* @param accessGrant
* @return
*/
@Override
public Connection<WeChat> createConnection(AccessGrant accessGrant) {
return new OAuth2Connection<WeChat>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(),
accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));
}
/**
* @param data
* @return
*/
@Override
public Connection<WeChat> createConnection(ConnectionData data) {
return new OAuth2Connection<WeChat>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
}
private ApiAdapter<WeChat> getApiAdapter(String providerUserId) {
return new WeChatAdapter(providerUserId);
}
private OAuth2ServiceProvider<WeChat> getOAuth2ServiceProvider() {
return (OAuth2ServiceProvider<WeChat>) getServiceProvider();
}
}
@@ -0,0 +1,132 @@
/**
*
*/
package com.micro.fast.security.core.social.wechat.connet;
import java.nio.charset.Charset;
import java.util.Map;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.social.oauth2.AccessGrant;
import org.springframework.social.oauth2.OAuth2Parameters;
import org.springframework.social.oauth2.OAuth2Template;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* 完成微信的OAuth2认证流程的模板类。国内厂商实现的OAuth2每个都不同, spring默认提供的OAuth2Template适应不了,只能针对每个厂商自己微调。
* @author lsy
*/
public class WeChatOAuth2Template extends OAuth2Template {
private String clientId;
private String clientSecret;
private String accessTokenUrl;
private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
private Logger logger = LoggerFactory.getLogger(getClass());
public WeChatOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
setUseParametersForClientAuthentication(true);
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessTokenUrl = accessTokenUrl;
}
/**
* @see org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(java.lang.String, java.lang.String, org.springframework.util.MultiValueMap)
*/
@Override
public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,
MultiValueMap<String, String> parameters) {
StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);
accessTokenRequestUrl.append("?appid="+clientId);
accessTokenRequestUrl.append("&secret="+clientSecret);
accessTokenRequestUrl.append("&code="+authorizationCode);
accessTokenRequestUrl.append("&grant_type=authorization_code");
accessTokenRequestUrl.append("&redirect_uri="+redirectUri);
return getAccessToken(accessTokenRequestUrl);
}
@Override
public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
refreshTokenUrl.append("?appid="+clientId);
refreshTokenUrl.append("&grant_type=refresh_token");
refreshTokenUrl.append("&refresh_token="+refreshToken);
return getAccessToken(refreshTokenUrl);
}
@SuppressWarnings("unchecked")
private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
logger.info("获取access_token, 请求URL: "+accessTokenRequestUrl.toString());
String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
logger.info("获取access_token, 响应内容: "+response);
Map<String, Object> result = null;
try {
result = new ObjectMapper().readValue(response, Map.class);
} catch (Exception e) {
e.printStackTrace();
}
//返回错误码时直接返回空
if(StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))){
String errcode = MapUtils.getString(result, "errcode");
String errmsg = MapUtils.getString(result, "errmsg");
throw new RuntimeException("获取access token失败, errcode:"+errcode+", errmsg:"+errmsg);
}
WeChatAccessGrant accessToken = new WeChatAccessGrant(
MapUtils.getString(result, "access_token"),
MapUtils.getString(result, "scope"),
MapUtils.getString(result, "refresh_token"),
MapUtils.getLong(result, "expires_in"));
accessToken.setOpenId(MapUtils.getString(result, "openid"));
return accessToken;
}
/**
* 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
*/
@Override
public String buildAuthenticateUrl(OAuth2Parameters parameters) {
String url = super.buildAuthenticateUrl(parameters);
url = url + "&appid="+clientId+"&scope=snsapi_login";
return url;
}
@Override
public String buildAuthorizeUrl(OAuth2Parameters parameters) {
return buildAuthenticateUrl(parameters);
}
/**
* 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。
*/
@Override
protected RestTemplate createRestTemplate() {
RestTemplate restTemplate = super.createRestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
}
@@ -0,0 +1,43 @@
/**
*
*/
package com.micro.fast.security.core.social.wechat.connet;
import com.micro.fast.security.core.social.wechat.api.WeChat;
import com.micro.fast.security.core.social.wechat.api.impl.WeChatImpl;
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
/**
*
* 微信的OAuth2流程处理器的提供器,供spring social的connect体系调用
*
* @author zhailiang
*
*/
public class WeChatServiceProvider extends AbstractOAuth2ServiceProvider<WeChat> {
/**
* 微信获取授权码的url
*/
private static final String URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect";
/**
* 微信获取accessToken的url
*/
private static final String URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";
/**
* @param appId
* @param appSecret
*/
public WeChatServiceProvider(String appId, String appSecret) {
super(new WeChatOAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN));
}
@Override
public WeChat getApi(String accessToken) {
return new WeChatImpl(accessToken);
}
}
@@ -0,0 +1 @@
package com.micro.fast.security.core.social.wechat.connet;
@@ -0,0 +1 @@
package com.micro.fast.security.core.social.wechat;
@@ -0,0 +1,37 @@
package com.micro.fast.security.core.validate.code;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 验证码的存储
* @author lsy
*/
public interface ValidateCodeRepository {
String SMS_CODE = "sms";
String IMAGE_CODE = "image";
/**
* 保存验证码
* @param request
* @param code
* @param validateCodeType
*/
void save(ServletWebRequest request, ValidateCode code,String validateCodeType);
/**
* 获取验证码
* @param request
* @param validateCodeType
* @return
*/
ValidateCode get(ServletWebRequest request,String validateCodeType);
/**
* 删除验证码
* @param request
* @param validateCodeType
*/
void remove(ServletWebRequest request,String validateCodeType);
}
@@ -2,14 +2,17 @@ package com.micro.fast.security.core.validate.code.filter;
import com.micro.fast.security.core.master.SecurityConstants;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.exception.ValidateCodeException;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import com.micro.fast.security.core.validate.code.processor.ValidateCodeProcessor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -18,6 +21,7 @@ import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.*;
@@ -31,8 +35,13 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
public static final String IMAGE = "IMAGE";
public static final String SMS = "SMS";
private AuthenticationFailureHandler authenticationFailureHandler;
private ValidateCodeRepository validateCodeRepository;
/**
* session操作工具类
*/
@@ -86,7 +95,6 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
, HttpServletResponse httpServletResponse
, FilterChain filterChain)
throws ServletException, IOException {
//判断是否需要验证码校验
boolean filter = false;
//进行校验的类型
@@ -125,7 +133,10 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
* @param validateType
*/
private void validate(ServletWebRequest servletWebRequest, String validateType) {
ValidateCode validateCode = (ValidateCode) (sessionStrategy.getAttribute(servletWebRequest, ValidateCodeProcessor.SESSION_KEY_PREFIX + validateType));
//
//获取存储的验证码
ValidateCode validateCode = validateCodeRepository.get(servletWebRequest, validateType.toLowerCase());
String code = servletWebRequest.getParameter(validateType.toLowerCase()+"Code");
//session和用户传入的验证码不能为空
@@ -139,8 +150,8 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
if (validateCode.isExpire()) {
throw new ValidateCodeException("验证码已过期");
}
//一次校验完成之后删除验证码
sessionStrategy.removeAttribute(servletWebRequest, ValidateCodeProcessor.SESSION_KEY_PREFIX + validateType);
//如果校验通过之后删除验证码
validateCodeRepository.remove(servletWebRequest,validateType.toLowerCase());
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
@@ -166,4 +177,11 @@ public class ValidateCodeFilter extends OncePerRequestFilter implements Initiali
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
public ValidateCodeRepository getValidateCodeRepository() {
return validateCodeRepository;
}
public void setValidateCodeRepository(ValidateCodeRepository validateCodeRepository) {
this.validateCodeRepository = validateCodeRepository;
}
}
@@ -1,5 +1,6 @@
package com.micro.fast.security.core.validate.code.processor.impl;
import com.micro.fast.security.core.validate.code.ValidateCodeRepository;
import com.micro.fast.security.core.validate.code.pojo.ValidateCode;
import com.micro.fast.security.core.validate.code.processor.ValidateCodeProcessor;
import com.micro.fast.security.core.validate.code.util.ValidateCodeUtil;
@@ -20,10 +21,7 @@ import java.util.Map;
public abstract class AbstractValidateCodeProcessor <T extends ValidateCode> implements ValidateCodeProcessor {
/**
* 操作session的工具类
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 收集系统中所有{@link ValidateCodeUtil}的实现,并将bean的名字作为key
@@ -33,6 +31,9 @@ public abstract class AbstractValidateCodeProcessor <T extends ValidateCode> imp
private static final String VALIDATE_CODE_UTIL_SUFFIX = "CodeUtil";
@Autowired
private ValidateCodeRepository validateCodeRepository;
@Override
public void create(ServletWebRequest servletWebRequest) throws ServletRequestBindingException, IOException {
T generateCode = generateCode(servletWebRequest);
@@ -63,10 +64,7 @@ public abstract class AbstractValidateCodeProcessor <T extends ValidateCode> imp
ValidateCode saveValidateCode = new ValidateCode();
saveValidateCode.setCode(validateCode.getCode());
saveValidateCode.setExpireTime(validateCode.getExpireTime());
//向session中存储验证码
sessionStrategy.setAttribute(servletWebRequest
,SESSION_KEY_PREFIX+getProcessorType(servletWebRequest).toUpperCase()
,saveValidateCode);
validateCodeRepository.save(servletWebRequest,saveValidateCode,getProcessorType(servletWebRequest));
}
/**
@@ -18,7 +18,7 @@
<dependencies>
<dependency>
<groupId>com.micro.fast</groupId>
<artifactId>boot-starter-security-browser</artifactId>
<artifactId>boot-starter-security-app</artifactId>
<version>${micro.fast.version}</version>
</dependency>
<dependency>
@@ -44,7 +44,13 @@
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
</dependency>
</dependencies>
<!--jwt令牌解析-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
@@ -11,7 +11,6 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2;
*/
@SpringBootApplication
@RestController
@EnableSwagger2
public class BootStarterSecurityDemoApplication {
public static void main(String[] args) {
@@ -0,0 +1,14 @@
package com.micro.fast.security.demo;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.stereotype.Component;
@Component
public class DemoConnectionSignUp implements ConnectionSignUp {
@Override
public String execute(Connection<?> connection) {
//根据社交用户信息,默认创建用户,并返回用户的唯一标识
return connection.getDisplayName();
}
}
@@ -1,12 +1,19 @@
package com.micro.fast.security.demo.controller;
import com.fasterxml.jackson.annotation.JsonView;
import com.micro.fast.security.core.master.SecurityProperties;
import com.micro.fast.security.demo.pojo.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
@@ -14,11 +21,17 @@ import java.util.List;
@RestController
@RequestMapping("/user")
public class RestfulController {
@Autowired
private SecurityProperties securityProperties;
@GetMapping("/{id:\\d+}")//使用正则表达式指定传入的参数只能是数字
@JsonView(User.DetailView.class)
public User getInfo(@PathVariable("id") String id){
public User getInfo(@PathVariable("id") String id, HttpServletRequest request) throws UnsupportedEncodingException {
User user = new User("tom");
String authorization = request.getHeader("Authorization");
String token = StringUtils.substringAfter(authorization, "bearer ");
//解析jwt
Claims body = Jwts.parser().setSigningKey(securityProperties.getOauth2().getJwtSigningKey().getBytes("UTF-8")).parseClaimsJws(token).getBody();
return user;
}
@@ -49,7 +62,6 @@ public class RestfulController {
System.out.println(s);
});
}
System.out.println(user.getName());
System.out.println(user.getPassword());
System.out.println(user.getBirthday());
@@ -1,14 +1,14 @@
spring:
session:
store-type: none # 暂时不开启session存储
# session:
# store-type: redis
datasource:
url: jdbc:mysql://127.0.0.1:3317/micro?useUncoide=true&chracterEncoding=utf-8&useSSL=false
username: root
password: studyj2e
driver-class-name: com.mysql.jdbc.Driver
redis:
host: fthipw.natappfree.cc
port: 6379
# redis:
# host: fthipw.natappfree.cc
# port: 6379
#security:
# basic:
# enabled: true
@@ -20,4 +20,15 @@ ms:
code:
image:
length: 4
# 应用session的管理
server:
session:
# 超时时间,单位是秒,但是不能小于一分钟
timeout: 600
# token的配置
security:
oauth2:
client:
client-id: 授权给谁
client-secret: 授权给谁的密码
+2 -16
View File
@@ -5,25 +5,11 @@
目中引入该依赖进行相关配置即可使用.
### boot-starter-ssm项目的参数配置
得益于springboot的自动配置以及条件化装配的机制.我们可以扩展封装自己的ssm-boot,也是就是本项目中的commom,本项目的自定义扩展配置如下:
- `jdbc.master.username` 用户名(必要参数)
- `jdbc.master.password` 密码是使用common模块中的AESUtil加密过后的(必要参数)
- `jdbc.master.url`    数据库连接的url(必要参数)
- `jdbc.master.driverClass` 数据库连接驱动类(必要参数)
- `druid.master.servletInitParams` druid数据连接池的servlet的初始化参数,默认的druid账号密码是micro,fast.如果需要自定义可以传入自己的配
置,使用key:value的数组的形式.
- `druid.master.servletUrl` druid数据库连接池的访问路径,默认访问路径是/druid/*
- `druid.master.beanNames` druid要监控的bean的名字,多个使用逗号分割
- `druid.master.defaultAutoCommit` 事务是否自动提交,默认为true
- `druid.master.initialSize` 连接池的初始化大小,默认是20
- `druid.master.maxActive` 连接池的最大的数量,默认是50
- `druid.master.minIdle`连接池的最小空闲数量,默认是10
- `druid.master.maxWait` 当连接池中连接已经用完了,等待建立一个新连接的最大毫秒数,默认是1000
- `druid.master.minEvictableIdleTimeMillis` 等待到达多长时间释放连接池,默认是3600000
- 参考[druid官网的配置说明](https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter)
- `mybatis.typeAliasesPackage` pojo包所在的位置(必要参数),如果要指定多个包的话使用逗号分割
- `mybatis.mappers` mapper映射文件所在的路径(必要参数)
- `mybatis.pageHelperProperties` pagehelper分页插件的properties配置形式是key:value数组的形式,默认方言是mysql,如果需要自定义可以传入自己的配置,使用key:value的数
组的形式.
- 除此之外`mybatis`的`dao`层扫描的配置需要在项目中自行配置
- 除此之外`common`实现了主从两个数据源的配置,从数据库的配置只需将`master`替换为`slave`.主从数据源默认使用相同的`druid`监控视图.其中对于`beanbeanNames`的监控配置,`servletUrl`的配
置,`servletInitParams`的配置在`master`中进行配置,需要在使用的项目之中去除默认的数据源自动装配`@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)`
- 需要注意的是:当使用多数据源的时候,需要在使用的项目之中去除默认的数据源自动装配`@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)`
- 对于数据源的切换方法是使用aop的方式对`@SwitchDataSource('数据源的名字')`进行拦截动态切换数据源,主数据源的名字是`master`,从数据源的名字是`slave`.对于没有注解默认的数据源是`master`.
@@ -10,7 +10,7 @@ import org.springframework.context.annotation.Configuration;
* @since 1.0
*/
@Configuration
@ComponentScan(basePackages = {"com.micro.fast.common.config"})
@ComponentScan(basePackages = {"com.micro.fast.common"})
public class CommonApplication {
// public static void main(String[] args) {
// SpringApplication.run(CommonApplication.class, args);
@@ -41,7 +41,7 @@ public class DynamicDataSourceConfig {
/**
* 从数据源
*/
@Autowired(required = false)
@Autowired
@Qualifier(MASTER_DATA_SOURCE)
private DataSource masterDataSource;
@@ -63,7 +63,7 @@ public class DynamicDataSourceConfig {
dynamicDataSource.setDefaultTargetDataSource(dataSource);
dataSourceMap.put(MASTER,dataSource);
this.dataSourceMap.forEach((key,value) -> {
if ( DYNAMIC_DATA_SOURCE.equals(key)
if ( !DYNAMIC_DATA_SOURCE.equals(key)
&&!DATA_SOURCE.equals(key)
&& !MASTER.equals(key)){
dataSourceMap.put(key,value);
@@ -76,7 +76,7 @@ public class DynamicDataSourceConfig {
dataSourceMap.put(MASTER,masterDataSource);
dataSourceMap.put(SLAVE,dataSource);
this.dataSourceMap.forEach((key,value) -> {
if (DYNAMIC_DATA_SOURCE.equals(key)
if (!DYNAMIC_DATA_SOURCE.equals(key)
&&!DATA_SOURCE.equals(key)
&&!MASTER_DATA_SOURCE.equals(key)
&& !MASTER.equals(key)
@@ -0,0 +1,12 @@
package com.micro.fast.common.exception;
/**
* 系统级别的异常,比如数据哭插入失败
* @author lsy
*/
public class SystemException extends RuntimeException {
public SystemException(String message) {
super(message);
}
}

Some files were not shown because too many files have changed in this diff Show More