mirror of https://github.com/jeecgboot/jeecg-boot
打通三方登录
parent
3ac8ee304a
commit
184cf97304
|
@ -37,7 +37,7 @@ now(),
|
||||||
null,
|
null,
|
||||||
'3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
'3eacac0e-0de9-4727-9a64-6bdd4be2ee1f',
|
||||||
'client_secret_basic',
|
'client_secret_basic',
|
||||||
'refresh_token,authorization_code,password',
|
'refresh_token,authorization_code,password,app,phone,social',
|
||||||
'http://127.0.0.1:8080/jeecg-',
|
'http://127.0.0.1:8080/jeecg-',
|
||||||
'http://127.0.0.1:8080/',
|
'http://127.0.0.1:8080/',
|
||||||
'*',
|
'*',
|
||||||
|
|
|
@ -28,4 +28,9 @@ public class LoginType {
|
||||||
* 扫码登录
|
* 扫码登录
|
||||||
*/
|
*/
|
||||||
public static final String SCAN = "scan";
|
public static final String SCAN = "scan";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 所有联合登录,比如github\钉钉\企业微信\微信
|
||||||
|
*/
|
||||||
|
public static final String SOCIAL = "social";
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,14 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||||
import com.nimbusds.jose.jwk.source.JWKSource;
|
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||||
import com.nimbusds.jose.proc.SecurityContext;
|
import com.nimbusds.jose.proc.SecurityContext;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.jeecg.config.security.app.AppGrantAuthenticationConvert;
|
||||||
|
import org.jeecg.config.security.app.AppGrantAuthenticationProvider;
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationConvert;
|
import org.jeecg.config.security.password.PasswordGrantAuthenticationConvert;
|
||||||
import org.jeecg.config.security.password.PasswordGrantAuthenticationProvider;
|
import org.jeecg.config.security.password.PasswordGrantAuthenticationProvider;
|
||||||
|
import org.jeecg.config.security.phone.PhoneGrantAuthenticationConvert;
|
||||||
|
import org.jeecg.config.security.phone.PhoneGrantAuthenticationProvider;
|
||||||
|
import org.jeecg.config.security.social.SocialGrantAuthenticationConvert;
|
||||||
|
import org.jeecg.config.security.social.SocialGrantAuthenticationProvider;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
|
@ -17,6 +23,7 @@ import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
@ -47,7 +54,7 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@EnableMethodSecurity(jsr250Enabled = true, securedEnabled = true)
|
@EnableMethodSecurity
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@ -62,6 +69,12 @@ public class SecurityConfig {
|
||||||
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
|
||||||
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new PasswordGrantAuthenticationConvert())
|
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new PasswordGrantAuthenticationConvert())
|
||||||
.authenticationProvider(new PasswordGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
.authenticationProvider(new PasswordGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||||
|
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new PhoneGrantAuthenticationConvert())
|
||||||
|
.authenticationProvider(new PhoneGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||||
|
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new AppGrantAuthenticationConvert())
|
||||||
|
.authenticationProvider(new AppGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||||
|
.tokenEndpoint(tokenEndpoint -> tokenEndpoint.accessTokenRequestConverter(new SocialGrantAuthenticationConvert())
|
||||||
|
.authenticationProvider(new SocialGrantAuthenticationProvider(authorizationService, tokenGenerator())))
|
||||||
//开启OpenID Connect 1.0(其中oidc为OpenID Connect的缩写)。 访问 /.well-known/openid-configuration即可获取认证信息
|
//开启OpenID Connect 1.0(其中oidc为OpenID Connect的缩写)。 访问 /.well-known/openid-configuration即可获取认证信息
|
||||||
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
|
.oidc(Customizer.withDefaults()); // Enable OpenID Connect 1.0
|
||||||
http
|
http
|
||||||
|
@ -153,6 +166,7 @@ public class SecurityConfig {
|
||||||
config.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
config.setAllowedMethods(Arrays.asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||||
return config;
|
return config;
|
||||||
}))
|
}))
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
|
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,11 +68,11 @@ public class AppGrantAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
PasswordGrantAuthenticationToken passwordGrantAuthenticationToken = (PasswordGrantAuthenticationToken) authentication;
|
AppGrantAuthenticationToken appGrantAuthenticationToken = (AppGrantAuthenticationToken) authentication;
|
||||||
Map<String, Object> additionalParameter = passwordGrantAuthenticationToken.getAdditionalParameters();
|
Map<String, Object> additionalParameter = appGrantAuthenticationToken.getAdditionalParameters();
|
||||||
|
|
||||||
// 授权类型
|
// 授权类型
|
||||||
AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();
|
AuthorizationGrantType authorizationGrantType = appGrantAuthenticationToken.getGrantType();
|
||||||
// 用户名
|
// 用户名
|
||||||
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
String username = (String) additionalParameter.get(OAuth2ParameterNames.USERNAME);
|
||||||
// 密码
|
// 密码
|
||||||
|
@ -105,7 +105,7 @@ public class AppGrantAuthenticationProvider implements AuthenticationProvider {
|
||||||
throw new JeecgCaptchaException(HttpStatus.PRECONDITION_FAILED.value(), "验证码错误");
|
throw new JeecgCaptchaException(HttpStatus.PRECONDITION_FAILED.value(), "验证码错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(appGrantAuthenticationToken);
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
||||||
|
@ -132,7 +132,7 @@ public class AppGrantAuthenticationProvider implements AuthenticationProvider {
|
||||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||||
.authorizationGrantType(authorizationGrantType)
|
.authorizationGrantType(authorizationGrantType)
|
||||||
.authorizedScopes(requestScopeSet)
|
.authorizedScopes(requestScopeSet)
|
||||||
.authorizationGrant(passwordGrantAuthenticationToken);
|
.authorizationGrant(appGrantAuthenticationToken);
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
.principalName(clientPrincipal.getName())
|
.principalName(clientPrincipal.getName())
|
||||||
|
@ -212,7 +212,7 @@ public class AppGrantAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(Class<?> authentication) {
|
public boolean supports(Class<?> authentication) {
|
||||||
return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
return AppGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||||
|
|
|
@ -36,8 +36,7 @@ public class PhoneGrantAuthenticationConvert implements AuthenticationConverter
|
||||||
|
|
||||||
// 验证码
|
// 验证码
|
||||||
String captcha = parameters.getFirst("captcha");
|
String captcha = parameters.getFirst("captcha");
|
||||||
if (!StringUtils.hasText(captcha) ||
|
if (!StringUtils.hasText(captcha)) {
|
||||||
parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
|
|
||||||
throw new OAuth2AuthenticationException("无效请求,验证码不能为空!");
|
throw new OAuth2AuthenticationException("无效请求,验证码不能为空!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,11 +68,11 @@ public class PhoneGrantAuthenticationProvider implements AuthenticationProvider
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
PasswordGrantAuthenticationToken passwordGrantAuthenticationToken = (PasswordGrantAuthenticationToken) authentication;
|
PhoneGrantAuthenticationToken phoneGrantAuthenticationToken = (PhoneGrantAuthenticationToken) authentication;
|
||||||
Map<String, Object> additionalParameter = passwordGrantAuthenticationToken.getAdditionalParameters();
|
Map<String, Object> additionalParameter = phoneGrantAuthenticationToken.getAdditionalParameters();
|
||||||
|
|
||||||
// 授权类型
|
// 授权类型
|
||||||
AuthorizationGrantType authorizationGrantType = passwordGrantAuthenticationToken.getGrantType();
|
AuthorizationGrantType authorizationGrantType = phoneGrantAuthenticationToken.getGrantType();
|
||||||
// 手机号
|
// 手机号
|
||||||
String phone = (String) additionalParameter.get("mobile");
|
String phone = (String) additionalParameter.get("mobile");
|
||||||
|
|
||||||
|
@ -102,7 +102,7 @@ public class PhoneGrantAuthenticationProvider implements AuthenticationProvider
|
||||||
throw new JeecgBootException("手机验证码错误");
|
throw new JeecgBootException("手机验证码错误");
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(passwordGrantAuthenticationToken);
|
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(phoneGrantAuthenticationToken);
|
||||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
|
|
||||||
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
||||||
|
@ -118,7 +118,7 @@ public class PhoneGrantAuthenticationProvider implements AuthenticationProvider
|
||||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||||
.authorizationGrantType(authorizationGrantType)
|
.authorizationGrantType(authorizationGrantType)
|
||||||
.authorizedScopes(requestScopeSet)
|
.authorizedScopes(requestScopeSet)
|
||||||
.authorizationGrant(passwordGrantAuthenticationToken);
|
.authorizationGrant(phoneGrantAuthenticationToken);
|
||||||
|
|
||||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
.principalName(clientPrincipal.getName())
|
.principalName(clientPrincipal.getName())
|
||||||
|
@ -195,7 +195,7 @@ public class PhoneGrantAuthenticationProvider implements AuthenticationProvider
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(Class<?> authentication) {
|
public boolean supports(Class<?> authentication) {
|
||||||
return PasswordGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
return PhoneGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.jeecg.config.security.social;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.jeecg.config.security.LoginType;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author EightMonth
|
||||||
|
* @date 2024/1/1
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SocialGrantAuthenticationConvert implements AuthenticationConverter {
|
||||||
|
@Override
|
||||||
|
public Authentication convert(HttpServletRequest request) {
|
||||||
|
|
||||||
|
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
|
||||||
|
if (!LoginType.SOCIAL.equals(grantType)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
|
//从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||||
|
MultiValueMap<String, String> parameters = getParameters(request);
|
||||||
|
|
||||||
|
String token = parameters.getFirst("token");
|
||||||
|
if (!StringUtils.hasText(token)) {
|
||||||
|
throw new OAuth2AuthenticationException("无效请求,三方token不能为空!");
|
||||||
|
}
|
||||||
|
|
||||||
|
String source = parameters.getFirst("thirdType");
|
||||||
|
if (!StringUtils.hasText(source)) {
|
||||||
|
throw new OAuth2AuthenticationException("无效请求,三方来源不能为空!");
|
||||||
|
}
|
||||||
|
|
||||||
|
//收集要传入PhoneGrantAuthenticationToken构造方法的参数,
|
||||||
|
//该参数接下来在PhoneGrantAuthenticationProvider中使用
|
||||||
|
Map<String, Object> additionalParameters = new HashMap<>();
|
||||||
|
//遍历从request中提取的参数,排除掉grant_type、client_id、code等字段参数,其他参数收集到additionalParameters中
|
||||||
|
parameters.forEach((key, value) -> {
|
||||||
|
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||||
|
!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||||
|
!key.equals(OAuth2ParameterNames.CODE)) {
|
||||||
|
additionalParameters.put(key, value.get(0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//返回自定义的PhoneGrantAuthenticationToken对象
|
||||||
|
return new SocialGrantAuthenticationToken(clientPrincipal, additionalParameters);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*从request中提取请求参数,然后存入MultiValueMap<String, String>
|
||||||
|
*/
|
||||||
|
private static MultiValueMap<String, String> getParameters(HttpServletRequest request) {
|
||||||
|
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
|
||||||
|
parameterMap.forEach((key, values) -> {
|
||||||
|
if (values.length > 0) {
|
||||||
|
for (String value : values) {
|
||||||
|
parameters.add(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,253 @@
|
||||||
|
package org.jeecg.config.security.social;
|
||||||
|
|
||||||
|
import com.auth0.jwt.JWT;
|
||||||
|
import com.auth0.jwt.interfaces.DecodedJWT;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jeecg.common.api.CommonAPI;
|
||||||
|
import org.jeecg.common.constant.CommonConstant;
|
||||||
|
import org.jeecg.common.exception.JeecgBootException;
|
||||||
|
import org.jeecg.common.system.vo.LoginUser;
|
||||||
|
import org.jeecg.common.system.vo.SysDepartModel;
|
||||||
|
import org.jeecg.common.util.RedisUtil;
|
||||||
|
import org.jeecg.common.util.oConvertUtils;
|
||||||
|
import org.jeecg.config.JeecgBaseConfig;
|
||||||
|
import org.jeecg.config.security.password.PasswordGrantAuthenticationToken;
|
||||||
|
import org.jeecg.modules.base.service.BaseCommonService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.oauth2.core.*;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author EightMonth
|
||||||
|
* @date 2024/1/1
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SocialGrantAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2";
|
||||||
|
|
||||||
|
private final OAuth2AuthorizationService authorizationService;
|
||||||
|
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||||
|
@Autowired
|
||||||
|
private CommonAPI commonAPI;
|
||||||
|
@Autowired
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
@Autowired
|
||||||
|
private JeecgBaseConfig jeecgBaseConfig;
|
||||||
|
@Autowired
|
||||||
|
private BaseCommonService baseCommonService;
|
||||||
|
|
||||||
|
public SocialGrantAuthenticationProvider(OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
|
||||||
|
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||||
|
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
|
||||||
|
this.authorizationService = authorizationService;
|
||||||
|
this.tokenGenerator = tokenGenerator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||||
|
SocialGrantAuthenticationToken socialGrantAuthenticationToken = (SocialGrantAuthenticationToken) authentication;
|
||||||
|
Map<String, Object> additionalParameter = socialGrantAuthenticationToken.getAdditionalParameters();
|
||||||
|
|
||||||
|
// 授权类型
|
||||||
|
AuthorizationGrantType authorizationGrantType = socialGrantAuthenticationToken.getGrantType();
|
||||||
|
// 三方token
|
||||||
|
String token = (String) additionalParameter.get("token");
|
||||||
|
// 三方来源
|
||||||
|
String source = (String) additionalParameter.get("thirdType");
|
||||||
|
|
||||||
|
//请求参数权限范围
|
||||||
|
String requestScopesStr = (String)additionalParameter.getOrDefault(OAuth2ParameterNames.SCOPE, "*");
|
||||||
|
//请求参数权限范围专场集合
|
||||||
|
Set<String> requestScopeSet = Stream.of(requestScopesStr.split(" ")).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
DecodedJWT jwt = JWT.decode(token);
|
||||||
|
String username = jwt.getClaim("username").asString();
|
||||||
|
|
||||||
|
LoginUser loginUser = commonAPI.getUserByName(username);
|
||||||
|
// 检查用户可行性
|
||||||
|
checkUserIsEffective(loginUser);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(socialGrantAuthenticationToken);
|
||||||
|
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||||
|
|
||||||
|
if (!registeredClient.getAuthorizationGrantTypes().contains(authorizationGrantType)) {
|
||||||
|
throw new JeecgBootException("非法登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
//由于在上面已验证过用户名、密码,现在构建一个已认证的对象UsernamePasswordAuthenticationToken
|
||||||
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken.authenticated(loginUser,clientPrincipal,new ArrayList<>());
|
||||||
|
|
||||||
|
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
|
||||||
|
.registeredClient(registeredClient)
|
||||||
|
.principal(usernamePasswordAuthenticationToken)
|
||||||
|
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||||
|
.authorizationGrantType(authorizationGrantType)
|
||||||
|
.authorizedScopes(requestScopeSet)
|
||||||
|
.authorizationGrant(socialGrantAuthenticationToken);
|
||||||
|
|
||||||
|
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||||
|
.principalName(clientPrincipal.getName())
|
||||||
|
.authorizedScopes(requestScopeSet)
|
||||||
|
.attribute(Principal.class.getName(), loginUser.getUsername())
|
||||||
|
.authorizationGrantType(authorizationGrantType);
|
||||||
|
|
||||||
|
|
||||||
|
// ----- Access token -----
|
||||||
|
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
|
||||||
|
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
|
||||||
|
if (generatedAccessToken == null) {
|
||||||
|
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||||
|
"无法生成访问token,请联系管理系。", ERROR_URI);
|
||||||
|
throw new OAuth2AuthenticationException(error);
|
||||||
|
}
|
||||||
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
|
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
|
||||||
|
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
|
||||||
|
if (generatedAccessToken instanceof ClaimAccessor) {
|
||||||
|
authorizationBuilder.token(accessToken, (metadata) -> {
|
||||||
|
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
authorizationBuilder.accessToken(accessToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Refresh token -----
|
||||||
|
OAuth2RefreshToken refreshToken = null;
|
||||||
|
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
|
||||||
|
// 不向公共客户端颁发刷新令牌
|
||||||
|
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
|
||||||
|
|
||||||
|
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
|
||||||
|
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
|
||||||
|
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
|
||||||
|
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||||
|
"无法生成刷新token,请联系管理员。", ERROR_URI);
|
||||||
|
throw new OAuth2AuthenticationException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
|
||||||
|
authorizationBuilder.refreshToken(refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
OAuth2Authorization authorization = authorizationBuilder.build();
|
||||||
|
|
||||||
|
authorizationService.save(authorization);
|
||||||
|
|
||||||
|
baseCommonService.addLog("用户名: " + loginUser.getUsername() + ",登录成功!", CommonConstant.LOG_TYPE_1, null,loginUser);
|
||||||
|
|
||||||
|
Map<String, Object> addition = new HashMap<>();
|
||||||
|
// 设置登录用户信息
|
||||||
|
addition.put("userInfo", loginUser);
|
||||||
|
addition.put("sysAllDictItems", commonAPI.queryAllDictItems());
|
||||||
|
|
||||||
|
List<SysDepartModel> departs = commonAPI.queryUserDeparts(loginUser.getId());
|
||||||
|
addition.put("departs", departs);
|
||||||
|
if (departs == null || departs.size() == 0) {
|
||||||
|
addition.put("multi_depart", 0);
|
||||||
|
} else if (departs.size() == 1) {
|
||||||
|
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
||||||
|
addition.put("multi_depart", 1);
|
||||||
|
} else {
|
||||||
|
//查询当前是否有登录部门
|
||||||
|
if(oConvertUtils.isEmpty(loginUser.getOrgCode())){
|
||||||
|
commonAPI.updateUserDepart(loginUser.getUsername(), departs.get(0).getOrgCode(),null);
|
||||||
|
}
|
||||||
|
addition.put("multi_depart", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, addition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> authentication) {
|
||||||
|
return SocialGrantAuthenticationToken.class.isAssignableFrom(authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) {
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = null;
|
||||||
|
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
|
||||||
|
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
|
||||||
|
}
|
||||||
|
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
|
||||||
|
return clientPrincipal;
|
||||||
|
}
|
||||||
|
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录失败超出次数5 返回true
|
||||||
|
* @param username
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isLoginFailOvertimes(String username){
|
||||||
|
String key = CommonConstant.LOGIN_FAIL + username;
|
||||||
|
Object failTime = redisUtil.get(key);
|
||||||
|
if(failTime!=null){
|
||||||
|
Integer val = Integer.parseInt(failTime.toString());
|
||||||
|
if(val>5){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录登录失败次数
|
||||||
|
* @param username
|
||||||
|
*/
|
||||||
|
private void addLoginFailOvertimes(String username){
|
||||||
|
String key = CommonConstant.LOGIN_FAIL + username;
|
||||||
|
Object failTime = redisUtil.get(key);
|
||||||
|
Integer val = 0;
|
||||||
|
if(failTime!=null){
|
||||||
|
val = Integer.parseInt(failTime.toString());
|
||||||
|
}
|
||||||
|
// 10分钟
|
||||||
|
redisUtil.set(key, ++val, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验用户是否有效
|
||||||
|
*/
|
||||||
|
private void checkUserIsEffective(LoginUser loginUser) {
|
||||||
|
//情况1:根据用户信息查询,该用户不存在
|
||||||
|
if (Objects.isNull(loginUser)) {
|
||||||
|
baseCommonService.addLog("用户登录失败,用户不存在!", CommonConstant.LOG_TYPE_1, null);
|
||||||
|
throw new JeecgBootException("该用户不存在,请注册");
|
||||||
|
}
|
||||||
|
//情况2:根据用户信息查询,该用户已注销
|
||||||
|
//update-begin---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||||
|
if (CommonConstant.DEL_FLAG_1.equals(loginUser.getDelFlag())) {
|
||||||
|
//update-end---author:王帅 Date:20200601 for:if条件永远为falsebug------------
|
||||||
|
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已注销!", CommonConstant.LOG_TYPE_1, null);
|
||||||
|
throw new JeecgBootException("该用户已注销");
|
||||||
|
}
|
||||||
|
//情况3:根据用户信息查询,该用户已冻结
|
||||||
|
if (CommonConstant.USER_FREEZE.equals(loginUser.getStatus())) {
|
||||||
|
baseCommonService.addLog("用户登录失败,用户名:" + loginUser.getUsername() + "已冻结!", CommonConstant.LOG_TYPE_1, null);
|
||||||
|
throw new JeecgBootException("该用户已冻结");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.jeecg.config.security.social;
|
||||||
|
|
||||||
|
import org.jeecg.config.security.LoginType;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author EightMonth
|
||||||
|
* @date 2024/1/1
|
||||||
|
*/
|
||||||
|
public class SocialGrantAuthenticationToken extends OAuth2AuthorizationGrantAuthenticationToken {
|
||||||
|
|
||||||
|
public SocialGrantAuthenticationToken(Authentication clientPrincipal, Map<String, Object> additionalParameters) {
|
||||||
|
super(new AuthorizationGrantType(LoginType.SOCIAL), clientPrincipal, additionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
9
pom.xml
9
pom.xml
|
@ -67,7 +67,8 @@
|
||||||
<codegenerate.version>1.4.4</codegenerate.version>
|
<codegenerate.version>1.4.4</codegenerate.version>
|
||||||
<autopoi-web.version>1.4.7</autopoi-web.version>
|
<autopoi-web.version>1.4.7</autopoi-web.version>
|
||||||
<minio.version>8.5.7</minio.version>
|
<minio.version>8.5.7</minio.version>
|
||||||
<justauth-spring-boot-starter.version>1.3.4</justauth-spring-boot-starter.version>
|
<justauth-spring-boot-starter.version>1.4.0</justauth-spring-boot-starter.version>
|
||||||
|
<justauth.version>1.16.6</justauth.version>
|
||||||
<dom4j.version>1.6.1</dom4j.version>
|
<dom4j.version>1.6.1</dom4j.version>
|
||||||
<qiniu-java-sdk.version>7.4.0</qiniu-java-sdk.version>
|
<qiniu-java-sdk.version>7.4.0</qiniu-java-sdk.version>
|
||||||
<!-- Log4j2爆雷漏洞 -->
|
<!-- Log4j2爆雷漏洞 -->
|
||||||
|
@ -317,6 +318,12 @@
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 更新 justauth 修复github登录问题 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>me.zhyd.oauth</groupId>
|
||||||
|
<artifactId>JustAuth</artifactId>
|
||||||
|
<version>${justauth.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.squareup.okhttp3</groupId>
|
<groupId>com.squareup.okhttp3</groupId>
|
||||||
<artifactId>okhttp</artifactId>
|
<artifactId>okhttp</artifactId>
|
||||||
|
|
Loading…
Reference in New Issue