init
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.newyetai.security.access.intercept;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.SecurityMetadataSource;
|
||||
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
|
||||
import org.springframework.security.access.intercept.InterceptorStatusToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.exception.BusinessException;
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.util.ResponseUtils;
|
||||
import com.newyetai.common.util.UserContextHolder;
|
||||
import com.newyetai.security.access.request.AuthDtoUtils;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.security.util.SecurityPropertyUtils;
|
||||
import com.newyetai.security.util.SecurityUtils;
|
||||
import com.newyetai.usercenter.user.constant.UserTypeEnum;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
/**
|
||||
* 菜单安全拦截器
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Slf4j
|
||||
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
|
||||
|
||||
// ~ Static fields/initializers
|
||||
// =====================================================================================
|
||||
|
||||
private static final String FILTER_APPLIED =
|
||||
"__spring_security_filterSecurityInterceptor_filterApplied_2";
|
||||
|
||||
public static final String AUTH_URL = "/sys_security_auth";
|
||||
public static final String ERROR_URL = "/error";
|
||||
|
||||
// ~ Instance fields
|
||||
// ================================================================================================
|
||||
|
||||
private FilterInvocationSecurityMetadataSource securityMetadataSource;
|
||||
private boolean observeOncePerRequest = true;
|
||||
|
||||
// ~ Methods
|
||||
// ========================================================================================================
|
||||
|
||||
/**
|
||||
* Not used (we rely on IoC container lifecycle services instead)
|
||||
*
|
||||
* @param arg0 ignored
|
||||
*
|
||||
* @throws ServletException never thrown
|
||||
*/
|
||||
@Override
|
||||
public void init(FilterConfig arg0) throws ServletException {}
|
||||
|
||||
/**
|
||||
* Not used (we rely on IoC container lifecycle services instead)
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {}
|
||||
|
||||
/**
|
||||
* Method that is actually called by the filter chain. Simply delegates to the
|
||||
* {@link #invoke(FilterInvocation)} method.
|
||||
*
|
||||
* @param request the servlet request
|
||||
* @param response the servlet response
|
||||
* @param chain the filter chain
|
||||
*
|
||||
* @throws IOException if the filter chain fails
|
||||
* @throws ServletException if the filter chain fails
|
||||
*/
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
FilterInvocation fi = new FilterInvocation(request, response, chain);
|
||||
invoke(fi);
|
||||
}
|
||||
|
||||
public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
|
||||
return this.securityMetadataSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecurityMetadataSource obtainSecurityMetadataSource() {
|
||||
return this.securityMetadataSource;
|
||||
}
|
||||
|
||||
public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
|
||||
this.securityMetadataSource = newSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getSecureObjectClass() {
|
||||
return FilterInvocation.class;
|
||||
}
|
||||
|
||||
private RequestMatcher authRequestMatcher = new AntPathRequestMatcher(AUTH_URL, "POST");
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public void setUserLoginService(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
public void invoke(FilterInvocation fi) throws IOException, ServletException {
|
||||
if (fi.getRequestUrl().equals(ERROR_URL)) {
|
||||
log.info("访问资源不存在");
|
||||
throw new BadLoginException("访问资源不存在");
|
||||
}
|
||||
// 如果是其他系统,调用用户中心鉴权
|
||||
// 如果是用户中心,其他系统鉴权路径,
|
||||
HttpServletRequest request = fi.getRequest();
|
||||
HttpServletResponse response = fi.getResponse();
|
||||
// 外部系统调用用户中心鉴权
|
||||
boolean authInvoke = authRequestMatcher.matches(request);
|
||||
if (authInvoke) {
|
||||
FilterInvocation authFilterInvocation = AuthDtoUtils.analy(request);
|
||||
try {
|
||||
userLoginService.checkActive(request);
|
||||
InterceptorStatusToken token = super.beforeInvocation(authFilterInvocation);
|
||||
ResponseUtils.printJson(response, AlitaResult.success());
|
||||
return;
|
||||
} catch (BadLoginException be) {
|
||||
ResponseUtils.printJson(response, be.getResult());
|
||||
} catch (Exception e) {
|
||||
log.error("鉴权失败", e);
|
||||
ResponseUtils.printJson(response,
|
||||
AlitaResult.error(AlitaResult.LOGIN_ERROR_CODE, e.getMessage()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
boolean isAnno = auth.isAuthenticated() && SecurityPropertyUtils.isAnon(request);
|
||||
boolean isUnac = !isAnno && SecurityPropertyUtils.isUnac(request);
|
||||
boolean hasLogin = UserContextHolder.getLoginUser() != null;
|
||||
// 匿名路径 | 未激活路径且已登录 直接过
|
||||
boolean isAllowed =
|
||||
(request != null) && (request.getAttribute(FILTER_APPLIED) != null) && observeOncePerRequest
|
||||
|| isAnno
|
||||
|| (hasLogin && isUnac);
|
||||
if (isAllowed) {
|
||||
// filter already applied to this request and user wants us to observe
|
||||
// once-per-request handling, so don't re-do security checking
|
||||
fi.getChain().doFilter(request, response);
|
||||
} else {
|
||||
// 未登录 去登录
|
||||
Assert.isTrue(hasLogin, new BadLoginException(AlitaResult.unLogin()));
|
||||
// first time this request being called, so perform security checking
|
||||
if (request != null && observeOncePerRequest) {
|
||||
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
|
||||
}
|
||||
InterceptorStatusToken token = null;
|
||||
// 用户中心鉴权
|
||||
userLoginService.checkActive(request);
|
||||
token = super.beforeInvocation(fi);
|
||||
if (token == null) {
|
||||
// TODO token 为空,资源未被分配,又不在白名单(只要登录就可访问),默认无权访问
|
||||
// throw AccessDeniedException ;
|
||||
}
|
||||
try {
|
||||
fi.getChain().doFilter(request, response);
|
||||
} finally {
|
||||
super.finallyInvocation(token);
|
||||
}
|
||||
|
||||
super.afterInvocation(token, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether once-per-request handling will be observed. By default this is
|
||||
* <code>true</code>, meaning the <code>FilterSecurityInterceptor</code> will only execute
|
||||
* once-per-request. Sometimes users may wish it to execute more than once per request, such as
|
||||
* when JSP forwards are being used and filter security is desired on each included fragment of
|
||||
* the HTTP request.
|
||||
*
|
||||
* @return <code>true</code> (the default) if once-per-request is honoured, otherwise
|
||||
* <code>false</code> if <code>FilterSecurityInterceptor</code> will enforce
|
||||
* authorizations for each and every fragment of the HTTP request.
|
||||
*/
|
||||
public boolean isObserveOncePerRequest() {
|
||||
return observeOncePerRequest;
|
||||
}
|
||||
|
||||
public void setObserveOncePerRequest(boolean observeOncePerRequest) {
|
||||
this.observeOncePerRequest = observeOncePerRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.newyetai.security.access.metadata;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.newyetai.common.entity.BaseEntity;
|
||||
import com.newyetai.security.access.provider.ResourceConfigAttributeProvider;
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
import com.newyetai.usercenter.dto.Resource;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
|
||||
/**
|
||||
* 资源安全元数据源
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Component
|
||||
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
|
||||
public class ResourceSecurityMetadataSource {
|
||||
|
||||
private final List<ResourceConfigAttributeProvider> providers;
|
||||
|
||||
public Collection<ConfigAttribute> getAttributes(Object object,
|
||||
PermissionQueryDTO permissionQueryDTO) throws IllegalArgumentException {
|
||||
if (!this.supports(object.getClass())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Long resourceId = ((Resource) object).getId();
|
||||
Collection<ConfigAttribute> attributes =
|
||||
getComponentMap(object, permissionQueryDTO).get(resourceId);
|
||||
return attributes == null ? Collections.emptyList() : attributes;
|
||||
}
|
||||
|
||||
public Map<Long, Collection<ConfigAttribute>> getComponentMap(Object object,
|
||||
PermissionQueryDTO permissionQueryDTO) {
|
||||
AnnotationAwareOrderComparator.sort(providers);
|
||||
Map<Long, Collection<ConfigAttribute>> componentMap = Maps.newLinkedHashMap();
|
||||
providers.forEach(provider -> {
|
||||
// 选择支持的提供者
|
||||
if (!provider.supports(object.getClass())) {
|
||||
return;
|
||||
}
|
||||
Map<Long, Collection<ConfigAttribute>> map = provider.provide(permissionQueryDTO);
|
||||
if (map != null && !map.isEmpty()) {
|
||||
componentMap.putAll(map);
|
||||
}
|
||||
|
||||
});
|
||||
return componentMap;
|
||||
}
|
||||
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return Resource.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.newyetai.security.access.metadata;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.common.util.SpringContextHolder;
|
||||
import com.newyetai.security.access.provider.FilterConfigAttributeProvider;
|
||||
import com.newyetai.security.access.request.AuthDtoUtils;
|
||||
|
||||
/**
|
||||
* 菜单安全元数据源
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Component
|
||||
public class UrlFilterInvocationSecurityMetadataSource
|
||||
implements FilterInvocationSecurityMetadataSource {
|
||||
|
||||
@Autowired
|
||||
private List<FilterConfigAttributeProvider> providers;
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAllConfigAttributes() {
|
||||
/*Set<ConfigAttribute> allAttributes = new HashSet<ConfigAttribute>();
|
||||
|
||||
for (Map.Entry<RequestMatcher, Collection<ConfigAttribute>> entry : getRequestMap()
|
||||
.entrySet()) {
|
||||
allAttributes.addAll(entry.getValue());
|
||||
}*/
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAttributes(Object object) {
|
||||
final HttpServletRequest request = ((FilterInvocation) object).getRequest();
|
||||
Integer systemCode = AuthDtoUtils.getSystemCode(SpringContextHolder.getRequest());
|
||||
return getAttributes(request, systemCode);
|
||||
}
|
||||
|
||||
public Collection<ConfigAttribute> getAttributes(HttpServletRequest request, Integer systemCode) {
|
||||
for (FilterConfigAttributeProvider provider : providers) {
|
||||
for (Entry<String, Collection<ConfigAttribute>> entry : provider.provide(systemCode).entrySet()) {
|
||||
if (new AntPathRequestMatcher("/" + entry.getKey(), null).matches(request)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return FilterInvocation.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setProvider(List<FilterConfigAttributeProvider> providers) {
|
||||
AnnotationAwareOrderComparator.sort(providers);
|
||||
this.providers = providers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.newyetai.security.access.provider;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
|
||||
/**
|
||||
* 访问菜单权限信息提供者
|
||||
* 资源被谁配置列表(可扩展configAttribute,可以有role_(目前),resource_等)
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
public interface FilterConfigAttributeProvider {
|
||||
/**
|
||||
* 提供菜单权限信息,以Map结构返回,Key为路径,<br>
|
||||
* Value为对应的权限信息
|
||||
*
|
||||
* @return 权限信息
|
||||
*/
|
||||
Map<String, Collection<ConfigAttribute>> provide(Integer systemCode);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.access.provider;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
|
||||
/**
|
||||
* 授权信息提供者
|
||||
* 人被分配角色列表(后面可扩展authority,可以有role_(目前),resource_等)
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
public interface GrantedAuthorityProvider {
|
||||
|
||||
/**
|
||||
* 提供授权权限信息
|
||||
*
|
||||
* @param userDetails 用户信息
|
||||
* @return 授权信息
|
||||
*/
|
||||
Collection<? extends GrantedAuthority> provide(IUserDetail userDetails);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.newyetai.security.access.provider;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
|
||||
/**
|
||||
* 资源权限
|
||||
*
|
||||
* @author sr on 2020-09-04
|
||||
* @param <K>
|
||||
*/
|
||||
public interface IConfigAttribute<K extends Serializable> extends ConfigAttribute {
|
||||
|
||||
/**
|
||||
* 唯一标识
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-09-04
|
||||
*/
|
||||
K getKey();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.newyetai.security.access.provider;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 请求资源权限
|
||||
*
|
||||
* @author sr on 2020-09-07
|
||||
* @param <K>
|
||||
*/
|
||||
public interface IUrlConfigAttribute<K extends Serializable> extends IConfigAttribute<K> {
|
||||
|
||||
String getUrl();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.newyetai.security.access.provider;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
|
||||
/**
|
||||
* 资源权限信息提供者
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
public interface ResourceConfigAttributeProvider<K extends Serializable, T extends IConfigAttribute<K>> {
|
||||
/**
|
||||
* 提供资源权限信息
|
||||
*
|
||||
* @return 权限信息
|
||||
*/
|
||||
List<T> list(PermissionQueryDTO permissionQueryDTO);
|
||||
|
||||
/**
|
||||
* 支持类型
|
||||
*
|
||||
* @param clazz
|
||||
* @return
|
||||
* @author sr on 2019-11-05
|
||||
*/
|
||||
boolean supports(Class<?> clazz);
|
||||
|
||||
/**
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-09-07
|
||||
*/
|
||||
default Map<K, Collection<T>> provide(PermissionQueryDTO permissionQueryDTO) {
|
||||
return this.list(permissionQueryDTO)
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
IConfigAttribute::getKey, v -> {
|
||||
Collection<T> values = Sets.newHashSet();
|
||||
values.add(v);
|
||||
return values;
|
||||
},
|
||||
(v1, v2) -> {
|
||||
v1.addAll(v2);
|
||||
return v1;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.newyetai.security.access.provider.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.security.access.provider.ResourceConfigAttributeProvider;
|
||||
import com.newyetai.usercenter.authority.dto.ComponentPermissionDTO;
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
import com.newyetai.usercenter.authority.service.UcPermissionService;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.resource.dto.ComponentDTO;
|
||||
|
||||
/**
|
||||
* 默认组件权限信息提供者
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Order(100)
|
||||
@Component
|
||||
public class ComponentConfigAttributeProvider
|
||||
implements ResourceConfigAttributeProvider<Long, ComponentPermissionDTO> {
|
||||
|
||||
@Autowired
|
||||
private UcPermissionService ucPermissionService;
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return ComponentDTO.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = SecurityConstants.CacheName.COMPONENT_ATTRIBUTE_MAP_CACHE_KEY,
|
||||
key = SecurityConstants.CACHE_KEY_PERMISSION_ORG_SYSTEM)
|
||||
public List<ComponentPermissionDTO> list(PermissionQueryDTO permissionQueryDTO) {
|
||||
return ucPermissionService.listComponent(permissionQueryDTO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.newyetai.security.access.provider.impl;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.security.access.provider.ResourceConfigAttributeProvider;
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
import com.newyetai.usercenter.authority.dto.UrlPermissionDTO;
|
||||
import com.newyetai.usercenter.authority.service.UcPermissionService;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.resource.dto.UrlDTO;
|
||||
|
||||
/**
|
||||
* 默认菜单权限信息提供者
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Component
|
||||
@Order(100)
|
||||
public class UrlConfigAttributeProvider
|
||||
implements ResourceConfigAttributeProvider<Long, UrlPermissionDTO> {
|
||||
|
||||
@Autowired
|
||||
private UcPermissionService ucPermissionService;
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return UrlDTO.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = SecurityConstants.CacheName.URL_ATTRIBUTE_MAP_CACHE_KEY,
|
||||
key = SecurityConstants.CACHE_KEY_PERMISSION_ORG_SYSTEM)
|
||||
public List<UrlPermissionDTO> list(PermissionQueryDTO permissionQueryDTO) {
|
||||
return ucPermissionService.listUrl(permissionQueryDTO).getData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.newyetai.security.access.provider.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.security.access.provider.FilterConfigAttributeProvider;
|
||||
import com.newyetai.security.service.IPermissionSecurityService;
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
import com.newyetai.usercenter.authority.dto.UrlPermissionDTO;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.resource.enums.UrlTypeEnum;
|
||||
|
||||
/**
|
||||
* 默认菜单权限信息提供者
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Component
|
||||
@Order(100)
|
||||
public class UrlFilterConfigAttribueProvider implements FilterConfigAttributeProvider {
|
||||
|
||||
@Autowired
|
||||
private IPermissionSecurityService iPermissionSecurityService;
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = SecurityConstants.CacheName.URL_FILTER_ATTRIBUTE_MAP_CACHE_KEY,
|
||||
key = SecurityConstants.CACHE_KEY_SYSTEM)
|
||||
public Map<String, Collection<ConfigAttribute>> provide(
|
||||
Integer systemCode) {
|
||||
// 应用下所有授权信息
|
||||
PermissionQueryDTO permissionQueryDTO =
|
||||
PermissionQueryDTO.builder().systemCode(systemCode).build();
|
||||
AlitaResult<List<UrlPermissionDTO>> result =
|
||||
iPermissionSecurityService.listUrl(permissionQueryDTO);
|
||||
Assert.isTrue(result.getSuccess(), "资源权限获取失败");
|
||||
return result.getData().stream().filter(p -> StringUtils.isNotBlank(p.getLink())
|
||||
&& UrlTypeEnum.INTERFACE.getType().toString().equals(p.getResourceType()))
|
||||
.collect(Collectors.toMap(UrlPermissionDTO::getLink, v -> {
|
||||
Collection<ConfigAttribute> values = new HashSet<ConfigAttribute>();
|
||||
values.add(v);
|
||||
return values;
|
||||
}, (v1, v2) -> {
|
||||
v1.addAll(v2);
|
||||
return v1;
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.newyetai.security.access.provider.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.security.access.provider.GrantedAuthorityProvider;
|
||||
import com.newyetai.security.service.IRoleAllocationSecurityService;
|
||||
import com.newyetai.usercenter.authority.dto.RoleAllocationDTO;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
|
||||
/**
|
||||
* 默认用户授权信息提供者
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Component
|
||||
public class UserGrantedAuthorityProvider implements GrantedAuthorityProvider {
|
||||
|
||||
@Autowired
|
||||
private IRoleAllocationSecurityService iRoleAllocationSecurityService;
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = SecurityConstants.CacheName.USER_GRANTED_AUTHORITY_CACHE_KEY,
|
||||
key = SecurityConstants.CACHE_KEY_ORG_USER)
|
||||
public Collection<? extends GrantedAuthority> provide(IUserDetail userDetails) {
|
||||
AlitaResult<List<RoleAllocationDTO>> result =
|
||||
iRoleAllocationSecurityService.findByUserId(userDetails.getId());
|
||||
Assert.isTrue(result.getSuccess(), "用户权限获取失败");
|
||||
return result.getData();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.newyetai.security.access.request;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-09-09
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class AuthDTO implements Serializable{
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -6816336716189522975L;
|
||||
|
||||
private String url;
|
||||
|
||||
private String method;
|
||||
|
||||
private Integer systemCode;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.newyetai.security.access.request;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
|
||||
import com.newyetai.common.util.EnvironmentUtils;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public abstract class AuthDtoUtils {
|
||||
|
||||
public static final String PARAM_URL = "url";
|
||||
public static final String PARAM_METHOD = "method";
|
||||
public static final String PARAM_SYSTEMCODE = "systemCode";
|
||||
|
||||
/**
|
||||
* 转换request为认证对象
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public static AuthDTO convert(HttpServletRequest request) {
|
||||
return AuthDTO.builder().url(request.getServletPath()).method(request.getMethod())
|
||||
.systemCode(EnvironmentUtils.getSystemCode()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据请求对象解析成拦截器调用对象
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public static FilterInvocation analy(HttpServletRequest request) {
|
||||
return new FilterInvocation(request.getParameter(PARAM_URL),
|
||||
request.getParameter(PARAM_METHOD));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取请求的应用标识
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public static Integer getSystemCode(HttpServletRequest request) {
|
||||
String systemCodeStr = null;
|
||||
if (request == null || (systemCodeStr = request.getParameter(PARAM_SYSTEMCODE)) == null) {
|
||||
return EnvironmentUtils.getSystemCode();
|
||||
}
|
||||
return Integer.valueOf(systemCodeStr);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.newyetai.security.access.vote;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.vote.AbstractAccessDecisionManager;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.security.config.SecurityProperties;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
|
||||
/**
|
||||
* 默认访问决策管理器
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Component
|
||||
public class AccessDecisionManagerImpl extends AbstractAccessDecisionManager {
|
||||
|
||||
private List<AccessDecisionVoter<? extends Object>> decisionVoters;
|
||||
|
||||
@Autowired
|
||||
public AccessDecisionManagerImpl(List<AccessDecisionVoter<? extends Object>> decisionVoters, SecurityProperties securityProperties) {
|
||||
super(decisionVoters);
|
||||
this.decisionVoters = decisionVoters;
|
||||
this.setAllowIfAllAbstainDecisions(securityProperties.getAllowIfAllAbstainDecisions());
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
@Override
|
||||
public void decide(Authentication authentication, Object object,
|
||||
Collection<ConfigAttribute> configAttributes)
|
||||
throws AccessDeniedException, InsufficientAuthenticationException {
|
||||
Object principal = authentication.getPrincipal();
|
||||
if (principal instanceof SecurityUserDTO
|
||||
&& ((SecurityUserDTO) principal).getSupeiror()) {
|
||||
// 平台管理员放行
|
||||
return;
|
||||
}
|
||||
int deny = 0;
|
||||
if (!configAttributes.isEmpty()) {
|
||||
for (AccessDecisionVoter voter : getDecisionVoters()) {
|
||||
if (voter.supports(object.getClass())) {
|
||||
int result = voter.vote(authentication, object, configAttributes);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Voter: " + voter + ", returned: " + result);
|
||||
}
|
||||
switch (result) {
|
||||
case AccessDecisionVoter.ACCESS_GRANTED:
|
||||
return;
|
||||
case AccessDecisionVoter.ACCESS_DENIED:
|
||||
deny++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deny > 0) {
|
||||
throw new AccessDeniedException(
|
||||
messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
|
||||
}
|
||||
|
||||
checkAllowIfAllAbstainDecisions();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package com.newyetai.security.access.vote;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDecisionVoter;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.security.service.GrantedAuthorityService;
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
import com.newyetai.usercenter.dto.Resource;
|
||||
|
||||
/**
|
||||
* 资源角色投票器 重写RoleVoter,获取用户授权信息authorities方式更改
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Component
|
||||
public class ResourceRoleVoter implements AccessDecisionVoter<Object> {
|
||||
|
||||
@Autowired
|
||||
private GrantedAuthorityService grantedAuthorityService;
|
||||
|
||||
private String rolePrefix = "ROLE_";
|
||||
|
||||
|
||||
public String getRolePrefix() {
|
||||
return rolePrefix;
|
||||
}
|
||||
|
||||
public void setRolePrefix(String rolePrefix) {
|
||||
this.rolePrefix = rolePrefix;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(ConfigAttribute attribute) {
|
||||
if ((attribute.getAttribute() != null)
|
||||
&& attribute.getAttribute().startsWith(getRolePrefix())) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return FilterInvocation.class.isAssignableFrom(clazz) || Resource.class.isAssignableFrom(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int vote(Authentication authentication, Object object,
|
||||
Collection<ConfigAttribute> attributes) {
|
||||
if (authentication == null) {
|
||||
return ACCESS_DENIED;
|
||||
}
|
||||
|
||||
int result = ACCESS_ABSTAIN;
|
||||
Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);
|
||||
|
||||
for (ConfigAttribute attribute : attributes) {
|
||||
if (this.supports(attribute)) {
|
||||
result = ACCESS_DENIED;
|
||||
|
||||
for (GrantedAuthority authority : authorities) {
|
||||
if (attribute.getAttribute().equals(authority.getAuthority())) {
|
||||
return ACCESS_GRANTED;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Collection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {
|
||||
if (authentication.getPrincipal() instanceof IUserDetail) {
|
||||
IUserDetail userDetails = (IUserDetail) authentication.getPrincipal();
|
||||
return grantedAuthorityService.getGrantedAuthorities(userDetails);
|
||||
}
|
||||
return authentication.getAuthorities();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.newyetai.security.cas;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
/**
|
||||
* cas认证信息
|
||||
*
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
public class CasAuthenticationToken extends AbstractAuthenticationToken {
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = 4078096826251434490L;
|
||||
|
||||
private final Object principal;
|
||||
private String credentials;
|
||||
|
||||
public CasAuthenticationToken(Object principal, String credentials) {
|
||||
super(null);
|
||||
this.principal = principal;
|
||||
this.credentials = credentials;
|
||||
setAuthenticated(false);
|
||||
}
|
||||
|
||||
public CasAuthenticationToken(Object principal,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(authorities);
|
||||
this.principal = principal;
|
||||
this.credentials = null;
|
||||
super.setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return this.credentials;
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
super.setAuthenticated(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eraseCredentials() {
|
||||
super.eraseCredentials();
|
||||
credentials = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.newyetai.security.config;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
import com.newyetai.security.handle.MyAuthenctiationFailureHandler;
|
||||
import com.newyetai.security.mobile.MobileAuthenticationProvider;
|
||||
import com.newyetai.security.social.service.StandardClientRegistrationRepository;
|
||||
import com.newyetai.security.social.wechat.service.WechatOauthLoginProvider;
|
||||
import com.newyetai.security.ssb.SsbAuthenticationProvider;
|
||||
import com.newyetai.security.temptoken.TokenAuthenticationProvider;
|
||||
import com.newyetai.security.upassword.UpasswordAuthenticationProvider;
|
||||
import com.newyetai.security.util.SecurityPropertyUtils;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
/**
|
||||
* security配置类
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigurationPackage
|
||||
public class SecurityConfiguration {
|
||||
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
SecurityConfiguration(SecurityProperties securityProperties){
|
||||
this.securityProperties = securityProperties;
|
||||
SecurityPropertyUtils.setSecurityProperties(securityProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 声明密码加密方式
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
MyAuthenctiationFailureHandler myAuthenctiationFailureHandler() {
|
||||
return new MyAuthenctiationFailureHandler(securityProperties.getLoginPage());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = SecurityConstants.PROP_KEY_SECURITY_OAUTH2_USEDB, havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
StandardClientRegistrationRepository standardClientRegistrationRepository(UserLoginService userLoginService) {
|
||||
return new StandardClientRegistrationRepository(userLoginService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(UserLoginService.class)
|
||||
MobileAuthenticationProvider mobileAuthenticationProvider(UserLoginService userLoginService) {
|
||||
return new MobileAuthenticationProvider(userLoginService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(UserLoginService.class)
|
||||
WechatOauthLoginProvider wechatOauthLoginProvider(UserLoginService userLoginService) {
|
||||
return new WechatOauthLoginProvider(userLoginService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(UserLoginService.class)
|
||||
TokenAuthenticationProvider tokenAuthenticationProvider(UserLoginService userLoginService) {
|
||||
return new TokenAuthenticationProvider(userLoginService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(UserLoginService.class)
|
||||
UpasswordAuthenticationProvider upasswordAuthenticationProvider(UserLoginService userLoginService) {
|
||||
return new UpasswordAuthenticationProvider(userLoginService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(UserLoginService.class)
|
||||
SsbAuthenticationProvider ssbAuthenticationProvider(UserLoginService userLoginService) {
|
||||
return new SsbAuthenticationProvider(userLoginService);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.newyetai.security.config;
|
||||
|
||||
import com.newyetai.security.jwt.JwtProperties;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 安全相关配置项
|
||||
*
|
||||
* @author guhong
|
||||
*/
|
||||
@Component
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = SecurityProperties.PREFIX)
|
||||
public class SecurityProperties {
|
||||
|
||||
/** 配置前缀 **/
|
||||
public final static String PREFIX = "security";
|
||||
|
||||
/**
|
||||
* 匿名访问的url
|
||||
*/
|
||||
private String anonymous;
|
||||
|
||||
/**
|
||||
* 忽略的资源,直接跳过spring security权限校验,一般是用做静态资源
|
||||
*/
|
||||
private String ignoring;
|
||||
|
||||
/** 登录页面 **/
|
||||
private String loginPage;
|
||||
|
||||
private String loginSuccessPage;
|
||||
/** h5登录成功页 **/
|
||||
private String loginH5SuccessPage;
|
||||
|
||||
/** 登录请求路径 **/
|
||||
private String loginProcessingUrl;
|
||||
|
||||
private Boolean allowIfAllAbstainDecisions;
|
||||
|
||||
/** 域名 **/
|
||||
private String domain;
|
||||
|
||||
private Boolean superiorOnly;
|
||||
|
||||
/** 不记日志路径,contains匹配 **/
|
||||
private String notLogs;
|
||||
|
||||
private JwtProperties jwt = new JwtProperties();
|
||||
|
||||
/** 是否调用本地认证鉴权,默认本地;调用户中心配置false **/
|
||||
private Boolean useLocal;
|
||||
|
||||
private Oauth2Properties oauth2 = new Oauth2Properties();
|
||||
|
||||
public String[] getAnonymousArray() {
|
||||
return StringUtils.isBlank(anonymous) ? ArrayUtils.EMPTY_STRING_ARRAY : anonymous.split(",");
|
||||
}
|
||||
|
||||
public String[] getIgnoringArray() {
|
||||
return StringUtils.isBlank(ignoring) ? ArrayUtils.EMPTY_STRING_ARRAY : ignoring.split(",");
|
||||
}
|
||||
|
||||
@Data
|
||||
public class Oauth2Properties{
|
||||
/** 是否启用 **/
|
||||
private Boolean enable;
|
||||
/** oauth配置类型:true使用数据库配置,否则配置文件 **/
|
||||
private Boolean usedb;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package com.newyetai.security.config;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.security.access.intercept.FilterSecurityInterceptor;
|
||||
import com.newyetai.security.handle.MyAuthenctiationFailureHandler;
|
||||
import com.newyetai.security.handle.MyLoginSuccessHandler;
|
||||
import com.newyetai.security.handle.MyLoginUrlAuthenticationEntryPoint;
|
||||
import com.newyetai.security.handle.MyLogoutSuccessHandler;
|
||||
import com.newyetai.security.jwt.JwtAuthenticationTokenFilter;
|
||||
import com.newyetai.security.mobile.MobileAuthenticationFilter;
|
||||
import com.newyetai.security.mobile.MobileAuthenticationProvider;
|
||||
import com.newyetai.security.social.service.StandardAuthorizationCodeTokenResponseClient;
|
||||
import com.newyetai.security.social.service.StandardOAuth2UserService;
|
||||
import com.newyetai.security.social.service.StandardOauth2AuthorizationRequestResolver;
|
||||
import com.newyetai.security.ssb.SsbAuthenticationFilter;
|
||||
import com.newyetai.security.ssb.SsbAuthenticationProvider;
|
||||
import com.newyetai.security.temptoken.TokenAuthenticationFilter;
|
||||
import com.newyetai.security.temptoken.TokenAuthenticationProvider;
|
||||
import com.newyetai.security.upassword.UpasswordAuthenticationFilter;
|
||||
import com.newyetai.security.upassword.UpasswordAuthenticationProvider;
|
||||
import com.newyetai.swagger.config.SwaggerConfig;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
import com.newyetai.usercenter.websocket.util.WebSocketUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* security配置
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
@Autowired
|
||||
private MyLogoutSuccessHandler myLogoutSuccessHandler;
|
||||
|
||||
@Autowired
|
||||
private MyLoginSuccessHandler myLoginSuccessHandler;
|
||||
|
||||
@Autowired
|
||||
private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
|
||||
|
||||
@Autowired
|
||||
private FilterInvocationSecurityMetadataSource securityMetadataSource;
|
||||
|
||||
@Autowired
|
||||
private AccessDecisionManager accessDecisionManager;
|
||||
|
||||
@Autowired(required = false)
|
||||
private UpasswordAuthenticationProvider upasswordAuthenticationProvider;
|
||||
|
||||
@Autowired(required = false)
|
||||
private MobileAuthenticationProvider mobileAuthenticationProvider;
|
||||
|
||||
@Autowired(required = false)
|
||||
private TokenAuthenticationProvider tokenAuthenticationProvider;
|
||||
@Autowired
|
||||
private SsbAuthenticationProvider ssbAuthenticationProvider;
|
||||
|
||||
@Autowired
|
||||
private SwaggerConfig swaggerConfig;
|
||||
|
||||
@Autowired(required = false)
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private ClientRegistrationRepository standardClientRegistrationRepository;
|
||||
|
||||
@Override
|
||||
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
|
||||
// 添加cas认证提供者
|
||||
auth.authenticationProvider(upasswordAuthenticationProvider)
|
||||
.authenticationProvider(mobileAuthenticationProvider)
|
||||
.authenticationProvider(tokenAuthenticationProvider)
|
||||
.authenticationProvider(ssbAuthenticationProvider)
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) throws Exception {
|
||||
String[] ignoringArray = securityProperties.getIgnoringArray();
|
||||
// swagger 接口文档,不设置访问权限
|
||||
if (swaggerConfig.isSwaggerEnable() && StringUtils.isNotBlank(SwaggerConfig.ACCESS_PREFIX)) {
|
||||
ignoringArray = ArrayUtils.addAll(ignoringArray, SwaggerConfig.ACCESS_PREFIX.split(","));
|
||||
}
|
||||
// websocket
|
||||
if (StringUtils.isNotBlank(WebSocketUtils.getPath())) {
|
||||
ignoringArray = ArrayUtils.add(ignoringArray, WebSocketUtils.getPath());
|
||||
}
|
||||
// 忽略的资源,直接跳过spring security权限校验
|
||||
web.ignoring()
|
||||
.antMatchers(ignoringArray)/*.antMatchers(securityProperties.getAnonymousArray())*/;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
log.info("------WebSecurityConfigurer configure");
|
||||
http
|
||||
.logout()
|
||||
.logoutSuccessHandler(myLogoutSuccessHandler).and()
|
||||
// 自定义认证失败处理器
|
||||
.exceptionHandling().authenticationEntryPoint(
|
||||
new MyLoginUrlAuthenticationEntryPoint(securityProperties.getLoginPage()))
|
||||
.and();
|
||||
|
||||
if (BooleanUtils.isTrue(securityProperties.getOauth2().getEnable())) {
|
||||
// oauth2
|
||||
http.oauth2Login()
|
||||
.authorizationEndpoint()
|
||||
.authorizationRequestResolver(
|
||||
new StandardOauth2AuthorizationRequestResolver(
|
||||
this.standardClientRegistrationRepository,
|
||||
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI))
|
||||
.and()
|
||||
.tokenEndpoint()
|
||||
.accessTokenResponseClient(new StandardAuthorizationCodeTokenResponseClient())
|
||||
.and()
|
||||
.userInfoEndpoint().userService(new StandardOAuth2UserService()).and()
|
||||
.failureHandler(myAuthenctiationFailureHandler)
|
||||
.successHandler(myLoginSuccessHandler).and();
|
||||
}
|
||||
http.addFilterAfter(upasswordAuthenticationFilter(),
|
||||
UsernamePasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(mobileAuthenticationFilter(), UpasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(tokenAuthenticationFilter(), UpasswordAuthenticationFilter.class);
|
||||
http.addFilterAfter(ssbAuthenticationFilter(), UpasswordAuthenticationFilter.class);
|
||||
|
||||
// 禁用CSRF
|
||||
http.csrf().disable();
|
||||
http.headers().frameOptions().deny();
|
||||
http.headers().xssProtection().block(true);
|
||||
http.headers().contentTypeOptions();
|
||||
|
||||
// 拦截器
|
||||
FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor();
|
||||
http.addFilterAfter(securityInterceptor,
|
||||
org.springframework.security.web.access.intercept.FilterSecurityInterceptor.class);
|
||||
|
||||
http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);
|
||||
|
||||
// 启用jwt
|
||||
if (Boolean.TRUE.equals(securityProperties.getJwt().getEnable())) {
|
||||
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter =
|
||||
createJwtAuthenticationTokenFilter();
|
||||
http.addFilterAfter(jwtAuthenticationTokenFilter,
|
||||
AnonymousAuthenticationFilter.class);
|
||||
// 禁用session
|
||||
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private JwtAuthenticationTokenFilter createJwtAuthenticationTokenFilter() {
|
||||
return new JwtAuthenticationTokenFilter(securityProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截器
|
||||
*
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author sr on 2019-11-28
|
||||
*/
|
||||
private FilterSecurityInterceptor createFilterSecurityInterceptor() throws Exception {
|
||||
FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();
|
||||
securityInterceptor.setSecurityMetadataSource(securityMetadataSource);
|
||||
securityInterceptor.setAccessDecisionManager(accessDecisionManager);
|
||||
// httpSecurity中shareobject共用的authenticationBuilder
|
||||
securityInterceptor.setAuthenticationManager(this.authenticationManagerBean());
|
||||
securityInterceptor.setUserLoginService(userLoginService);
|
||||
securityInterceptor.afterPropertiesSet();
|
||||
return securityInterceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号登录请求过滤器
|
||||
*
|
||||
* @param authenticationManager
|
||||
* @return
|
||||
* @author sr on 2020-06-04
|
||||
* @throws Exception
|
||||
*/
|
||||
private MobileAuthenticationFilter mobileAuthenticationFilter() throws Exception {
|
||||
MobileAuthenticationFilter filter = new MobileAuthenticationFilter();
|
||||
addHandler(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
private void addHandler(AbstractAuthenticationProcessingFilter filter) throws Exception {
|
||||
filter.setAuthenticationManager(this.authenticationManagerBean());
|
||||
filter.setAuthenticationSuccessHandler(myLoginSuccessHandler);
|
||||
filter.setAuthenticationFailureHandler(myAuthenctiationFailureHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号密码
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author sr on 2021-06-22
|
||||
*/
|
||||
private UpasswordAuthenticationFilter upasswordAuthenticationFilter() throws Exception {
|
||||
UpasswordAuthenticationFilter filter = new UpasswordAuthenticationFilter();
|
||||
addHandler(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时token
|
||||
* @return
|
||||
* @throws Exception
|
||||
* @author sr on 2021-06-22
|
||||
*/
|
||||
private TokenAuthenticationFilter tokenAuthenticationFilter() throws Exception {
|
||||
TokenAuthenticationFilter filter = new TokenAuthenticationFilter();
|
||||
addHandler(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
private SsbAuthenticationFilter ssbAuthenticationFilter() throws Exception {
|
||||
SsbAuthenticationFilter filter = new SsbAuthenticationFilter();
|
||||
addHandler(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.newyetai.security.decision.manager;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
import com.newyetai.usercenter.dto.Resource;
|
||||
|
||||
/**
|
||||
* 权限决策管理器。<br>
|
||||
* 实现{@link org.springframework.security.access.SecurityMetadataSource}来<br>
|
||||
* 提供自己定义资源的权限信息,就可以实现权限决策判断
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
public interface SecurityDecisionManager {
|
||||
|
||||
/**
|
||||
* 决策给定的资源,当前登录用户是否有权限
|
||||
*
|
||||
* @param resource 资源
|
||||
* @return 结果。true为有权限,否则没有
|
||||
*/
|
||||
boolean decide(Resource resource, PermissionQueryDTO permissionQueryDTO);
|
||||
|
||||
/**
|
||||
* 根据资源获取权限信息
|
||||
*
|
||||
* @param resource 资源
|
||||
* @return 权限列表
|
||||
*/
|
||||
Collection<ConfigAttribute> findConfigAttributes(Resource resource, PermissionQueryDTO permissionQueryDTO);
|
||||
|
||||
/**
|
||||
* 决策给定的资源,给定用户是否有权限
|
||||
*
|
||||
* @param userDetails 用户实体
|
||||
* @param resource 资源
|
||||
* @return 结果。true为有权限,否则没有
|
||||
* @author sr on 2019-10-16
|
||||
*/
|
||||
boolean decide(UserDetails userDetails, Resource resource, PermissionQueryDTO permissionQueryDTO);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.newyetai.security.decision.manager.impl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.AccessDecisionManager;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import com.newyetai.security.access.metadata.ResourceSecurityMetadataSource;
|
||||
import com.newyetai.security.decision.manager.SecurityDecisionManager;
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
import com.newyetai.usercenter.dto.Resource;
|
||||
|
||||
/**
|
||||
* 权限决策管理器
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Service
|
||||
public class SecurityDecisionManagerImpl implements SecurityDecisionManager {
|
||||
|
||||
@Autowired
|
||||
private ResourceSecurityMetadataSource resourceSecurityMetadataSource;
|
||||
|
||||
@Autowired
|
||||
private AccessDecisionManager accessDecisionManager;
|
||||
|
||||
@Override
|
||||
public boolean decide(Resource resource, PermissionQueryDTO permissionQueryDTO) {
|
||||
return decide(SecurityContextHolder.getContext().getAuthentication(), resource, permissionQueryDTO);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean decide(UserDetails userDetails, Resource resource,
|
||||
PermissionQueryDTO permissionQueryDTO) {
|
||||
return decide(getAuthentication(userDetails), resource, permissionQueryDTO);
|
||||
}
|
||||
|
||||
private boolean decide(Authentication authentication, Resource resource,
|
||||
PermissionQueryDTO permissionQueryDTO) {
|
||||
try {
|
||||
Collection<ConfigAttribute> attributes = findConfigAttributes(resource, permissionQueryDTO);
|
||||
accessDecisionManager.decide(authentication, resource, attributes);
|
||||
return true;
|
||||
} catch (AccessDeniedException e) {
|
||||
return false;
|
||||
} catch (InsufficientAuthenticationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户认证信息
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2019-10-16
|
||||
*/
|
||||
protected Authentication getAuthentication(UserDetails userDetails) {
|
||||
return userDetails != null ? new UsernamePasswordAuthenticationToken(userDetails, null)
|
||||
: SecurityContextHolder.getContext().getAuthentication();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> findConfigAttributes(Resource resource,
|
||||
PermissionQueryDTO permissionQueryDTO) {
|
||||
Collection<ConfigAttribute> attributes = resource.getAttributes();
|
||||
if (CollectionUtils.isEmpty(attributes)
|
||||
&& resourceSecurityMetadataSource.supports(resource.getClass())) {
|
||||
return resourceSecurityMetadataSource.getAttributes(resource, permissionQueryDTO);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
62
src/main/java/com/newyetai/security/dto/RoleDTO.java
Normal file
62
src/main/java/com/newyetai/security/dto/RoleDTO.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package com.newyetai.security.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.newyetai.common.validation.Create;
|
||||
import com.newyetai.common.validation.Update;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 角色
|
||||
*
|
||||
* @author sr on 2019-11-05
|
||||
*/
|
||||
@ApiModel("角色")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class RoleDTO implements Serializable {
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -1148469623294607563L;
|
||||
|
||||
@ApiModelProperty("角色id")
|
||||
@NotNull(message = "角色id不能为空", groups = {Update.class})
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("名称")
|
||||
@NotBlank(message = "角色名称不能为空", groups = {Create.class})
|
||||
@Size(max = 128)
|
||||
private String name;
|
||||
|
||||
/** 类型 **/
|
||||
@JsonIgnore
|
||||
private String type;
|
||||
|
||||
@ApiModelProperty("是否启用")
|
||||
private Boolean enabled;
|
||||
|
||||
@ApiModelProperty("描述")
|
||||
@Size(max = 64, groups = {Create.class, Update.class})
|
||||
private String description;
|
||||
|
||||
@ApiModelProperty("标准角色类型:见role_standard_type")
|
||||
private String standardType;
|
||||
|
||||
@ApiModelProperty("创建时间")
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
102
src/main/java/com/newyetai/security/dto/SecurityUserDTO.java
Normal file
102
src/main/java/com/newyetai/security/dto/SecurityUserDTO.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package com.newyetai.security.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* userDetails
|
||||
*
|
||||
* @author sr on 2020-06-10
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Setter
|
||||
@ToString
|
||||
public class SecurityUserDTO extends UserDetailDTO implements Serializable, IUserDetail, UserDetails, OAuth2User {
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -4158044873830314665L;
|
||||
|
||||
/** 标签 **/
|
||||
private String tag;
|
||||
|
||||
@JsonIgnore
|
||||
private Collection<? extends GrantedAuthority> authorities;
|
||||
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Boolean getAccountNonExpired() {
|
||||
return super.getAccountNonExpired();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Boolean getAccountNonLocked() {
|
||||
return super.getAccountNonLocked();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Boolean getCredentialsNonExpired() {
|
||||
return super.getCredentialsNonExpired();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return authorities == null ? Sets.newHashSet() : authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return !Boolean.FALSE.equals(getAccountNonExpired());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return !Boolean.FALSE.equals(getAccountNonLocked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return !Boolean.FALSE.equals(getCredentialsNonExpired());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return !Boolean.FALSE.equals(getEnabled());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public String getName() {
|
||||
return getUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
@JsonIgnore
|
||||
public Map<String, Object> getAttributes() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
61
src/main/java/com/newyetai/security/dto/UserInfoDTO.java
Normal file
61
src/main/java/com/newyetai/security/dto/UserInfoDTO.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package com.newyetai.security.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* @author sr on 2019-10-22
|
||||
*/
|
||||
@ApiModel("用户信息")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UserInfoDTO implements Serializable{
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -684945131805466713L;
|
||||
|
||||
@ApiModelProperty("账号")
|
||||
private String username;
|
||||
|
||||
@ApiModelProperty("用户类型")
|
||||
private Integer userType;
|
||||
|
||||
@ApiModelProperty("昵称")
|
||||
private String nickname;
|
||||
|
||||
@ApiModelProperty("机构id")
|
||||
private Long orgId;
|
||||
|
||||
@ApiModelProperty("机构名称")
|
||||
private String orgName;
|
||||
|
||||
@ApiModelProperty("机构类型")
|
||||
private String orgType;
|
||||
|
||||
@ApiModelProperty("电话")
|
||||
private String phone;
|
||||
|
||||
@ApiModelProperty("邮箱")
|
||||
private String email;
|
||||
|
||||
@ApiModelProperty("是否已初始密码")
|
||||
private Boolean initialedPassword;
|
||||
|
||||
@ApiModelProperty("是否忽略初始密码")
|
||||
private Boolean ignoreInitPwd;
|
||||
|
||||
@ApiModelProperty("招聘岗位类型")
|
||||
private String recruitPositionType;
|
||||
|
||||
private String avatar;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.newyetai.security.expection;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
|
||||
/**
|
||||
* 登录信息异常
|
||||
*
|
||||
* @author sr on 2020-06-15
|
||||
*/
|
||||
public class BadLoginException extends AuthenticationException {
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -5461032165020284506L;
|
||||
|
||||
private AlitaResult<?> result;
|
||||
|
||||
public BadLoginException(String msg) {
|
||||
super(msg);
|
||||
result = AlitaResult.error(AlitaResult.LOGIN_ERROR_CODE, msg);
|
||||
}
|
||||
|
||||
public BadLoginException(String msg, Throwable t) {
|
||||
super(msg, t);
|
||||
result = AlitaResult.error(AlitaResult.LOGIN_ERROR_CODE, msg);
|
||||
}
|
||||
|
||||
public BadLoginException(AlitaResult<?> result) {
|
||||
super(result.getMsg());
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public AlitaResult<?> getResult(){
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.newyetai.security.expection;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.security.temptoken.dto.DuplUserToken;
|
||||
|
||||
/**
|
||||
* 多个登录机构异常
|
||||
*
|
||||
* @author sr on 2020-06-15
|
||||
*/
|
||||
public class DuplicateOrgException extends BadLoginException {
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -5461032165020284506L;
|
||||
|
||||
/** 用户信息 **/
|
||||
private final DuplUserToken duplUserToken;
|
||||
|
||||
public static final String MSG = "登录用户存在多机构";
|
||||
|
||||
public DuplicateOrgException(DuplUserToken duplUserToken) {
|
||||
super(AlitaResult.error(AlitaResult.DUPLICATE_ORG_CODE, MSG, duplUserToken));
|
||||
super.getResult().setErrorData(duplUserToken);
|
||||
this.duplUserToken = duplUserToken;
|
||||
}
|
||||
|
||||
public DuplUserToken getDuplUserToken() {
|
||||
return this.duplUserToken;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.newyetai.security.expection;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.security.temptoken.dto.DuplMobileToken;
|
||||
|
||||
/**
|
||||
* 多个登录用户异常
|
||||
*
|
||||
* @author sr on 2020-06-15
|
||||
*/
|
||||
public class DuplicateUserException extends BadLoginException {
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -5461032165020284506L;
|
||||
|
||||
/** 用户信息 **/
|
||||
private final DuplMobileToken duplMobileToken;
|
||||
|
||||
public static final String MSG = "登录用户存在多用户";
|
||||
|
||||
public DuplicateUserException(DuplMobileToken duplMobileToken) {
|
||||
super(AlitaResult.error(AlitaResult.DUPLICATE_USER_CODE,
|
||||
MSG, duplMobileToken));
|
||||
super.getResult().setErrorData(duplMobileToken);
|
||||
this.duplMobileToken = duplMobileToken;
|
||||
}
|
||||
|
||||
public DuplMobileToken getDuplMobileToken() {
|
||||
return this.duplMobileToken;
|
||||
}
|
||||
|
||||
public List<?> getUsers(){
|
||||
return this.duplMobileToken.getUsers();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.newyetai.security.handle;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.DisabledException;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.util.RequestUtils;
|
||||
import com.newyetai.common.util.ResponseUtils;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.security.expection.DuplicateOrgException;
|
||||
import com.newyetai.security.expection.DuplicateUserException;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author guhong
|
||||
* @date 2019/8/25
|
||||
*/
|
||||
@Slf4j
|
||||
public class MyAuthenctiationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||
|
||||
private static final String DIABLE_MSG = "用户被禁用";
|
||||
private static final String BAD_CREDENTIALS_MSG = "账号和密码不匹配,请重新登录";
|
||||
|
||||
|
||||
public MyAuthenctiationFailureHandler(String loginFormUrl) {
|
||||
super(loginFormUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException exception) throws IOException, ServletException {
|
||||
log.debug("登录失败:{}", exception);
|
||||
if (exception instanceof BadLoginException) {
|
||||
ResponseUtils.printJson(response, ((BadLoginException) exception).getResult());
|
||||
return;
|
||||
}
|
||||
if (RequestUtils.isAjaxRequest(request)) {
|
||||
ResponseUtils.printJson(response, AlitaResult.error(dealExceptionMsg(exception)));
|
||||
} else {
|
||||
super.onAuthenticationFailure(request, response, exception);
|
||||
}
|
||||
}
|
||||
|
||||
public String dealExceptionMsg(AuthenticationException exception) {
|
||||
if (exception instanceof DisabledException) {
|
||||
return DIABLE_MSG;
|
||||
}
|
||||
if (exception instanceof BadCredentialsException) {
|
||||
return BAD_CREDENTIALS_MSG;
|
||||
}
|
||||
return exception.getMessage();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.newyetai.security.handle;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.dto.CasTokenDTO;
|
||||
import com.newyetai.common.util.CookieUtils;
|
||||
import com.newyetai.common.util.JsonUtils;
|
||||
import com.newyetai.common.util.RequestUtils;
|
||||
import com.newyetai.common.util.ResponseUtils;
|
||||
import com.newyetai.security.config.SecurityProperties;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.jwt.JwtTokenUtils;
|
||||
import com.newyetai.security.ssb.SsbAuthenticationFilter;
|
||||
import com.newyetai.security.util.SecurityUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 登录成功处理器
|
||||
*
|
||||
* @author guhong
|
||||
* @date 2019/8/19
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MyLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||
|
||||
@Autowired
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
@Autowired
|
||||
private JwtTokenUtils jwtTokenUtils;
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws ServletException, IOException {
|
||||
Object principal = authentication.getPrincipal();
|
||||
|
||||
log.debug("------MyLoginSuccessHandler onAuthenticationSuccess userDTO: {}",
|
||||
JsonUtils.toJsonString(principal));
|
||||
CasTokenDTO caseTokenDTO = null;
|
||||
if (principal instanceof SecurityUserDTO) {
|
||||
SecurityUserDTO securityUserDto = (SecurityUserDTO) principal;
|
||||
caseTokenDTO = CasTokenDTO.builder().register(securityUserDto.getRegister())
|
||||
.token(jwtTokenUtils.generateToken(securityUserDto)).build();
|
||||
} else if (principal instanceof CasTokenDTO) {
|
||||
caseTokenDTO = (CasTokenDTO) principal;
|
||||
} else {
|
||||
throw new RuntimeException("数据异常");
|
||||
}
|
||||
String token = caseTokenDTO.getToken();
|
||||
this.dealCookie(token, request, response);
|
||||
if (RequestUtils.isAjaxRequest(request) || RequestUtils.isFeignRequest(request)) {
|
||||
// ajax
|
||||
ResponseUtils.printJson(response,
|
||||
AlitaResult.success(CasTokenDTO.builder().token(token).build()));
|
||||
} else {
|
||||
String successPage = securityProperties.getLoginSuccessPage();
|
||||
if (request.getRequestURI().contains(SsbAuthenticationFilter.LOGIN_URI)) {
|
||||
successPage = securityProperties.getLoginH5SuccessPage();
|
||||
}
|
||||
String successToUrl =
|
||||
SecurityUtils.dealSuccessUrl(request, successPage, token);
|
||||
response.sendRedirect(successToUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private void dealCookie(String token, HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
// 处理cookie
|
||||
String domainStr = RequestUtils.getRootDomain(request);
|
||||
String key = CookieUtils.getLoginTokenCookieKey();
|
||||
Cookie cookie = CookieUtils.createCookie(key, token, domainStr,
|
||||
null, jwtTokenUtils.getExpiration().intValue());
|
||||
//写入Cookie
|
||||
response.addCookie(cookie);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.newyetai.security.handle;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.util.RequestUtils;
|
||||
import com.newyetai.common.util.ResponseUtils;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
|
||||
/**
|
||||
* 自定义认证失败处理器
|
||||
* @author guhong
|
||||
* @date 2019/8/25
|
||||
*/
|
||||
public class MyLoginUrlAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
|
||||
|
||||
/**
|
||||
* @param loginFormUrl URL where the login page can be found. Should either be relative to the
|
||||
* web-app context path (include a leading {@code /}) or an absolute URL.
|
||||
*/
|
||||
public MyLoginUrlAuthenticationEntryPoint(String loginFormUrl) {
|
||||
super(loginFormUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||
AuthenticationException authException) throws IOException, ServletException {
|
||||
// ajax|feign
|
||||
if (RequestUtils.isAjaxRequest(request) || RequestUtils.isFeignRequest(request)) {
|
||||
if (authException instanceof BadLoginException) {
|
||||
ResponseUtils.printJson(response, ((BadLoginException) authException).getResult());
|
||||
return;
|
||||
}
|
||||
ResponseUtils.printJson(response,
|
||||
AlitaResult.error(AlitaResult.LOGIN_ERROR_CODE, "鉴权失败:" + authException.getMessage()));
|
||||
} else {
|
||||
super.commence(request, response, authException);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.newyetai.security.handle;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.util.CookieUtils;
|
||||
import com.newyetai.common.util.RequestUtils;
|
||||
import com.newyetai.common.util.ResponseUtils;
|
||||
|
||||
/**
|
||||
* @author guhong
|
||||
*/
|
||||
@Component
|
||||
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
//清空Cookie
|
||||
CookieUtils.removeCookie(CookieUtils.getLoginTokenCookieKey(),
|
||||
RequestUtils.getRootDomain(request), request, response);
|
||||
// ajax登出
|
||||
if (RequestUtils.isAjaxRequest(request)) {
|
||||
ResponseUtils.printJson(response, AlitaResult.success());
|
||||
} else {
|
||||
response.sendRedirect("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.newyetai.security.jwt;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.filter.GenericFilterBean;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.exception.ChangeTokenException;
|
||||
import com.newyetai.common.util.CookieUtils;
|
||||
import com.newyetai.common.util.RequestUtils;
|
||||
import com.newyetai.common.util.ResponseUtils;
|
||||
import com.newyetai.common.util.StrUtils;
|
||||
import com.newyetai.common.util.UserContextHolder;
|
||||
import com.newyetai.security.config.SecurityProperties;
|
||||
import com.newyetai.security.util.HostContextHolder;
|
||||
import com.newyetai.usercenter.constants.OrgConstants;
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* jwt过滤器 解析token,setContext
|
||||
*
|
||||
* @author sr on 2019-12-21
|
||||
*/
|
||||
@Slf4j
|
||||
public class JwtAuthenticationTokenFilter extends GenericFilterBean {
|
||||
|
||||
private SecurityProperties securityProperties;
|
||||
|
||||
public JwtAuthenticationTokenFilter(SecurityProperties securityProperties) {
|
||||
this.securityProperties = securityProperties;
|
||||
}
|
||||
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain chain) throws ServletException, IOException {}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
HttpServletRequest request = (HttpServletRequest) req;
|
||||
HttpServletResponse response = (HttpServletResponse) res;
|
||||
IUserDetail userDto = UserContextHolder.getLoginUser();
|
||||
String headerToken = request.getHeader(securityProperties.getJwt().getHeader());
|
||||
String feignHeader = request.getHeader(RequestUtils.FEIGN_REQUEST_HEADER_KEY);
|
||||
try {
|
||||
dealHostHeader(request);
|
||||
// 未登录,但有token
|
||||
if (userDto == null) {
|
||||
if (StrUtils.isNotNull(headerToken)) {
|
||||
log.debug("analyse header toke:{}", headerToken);
|
||||
userDto = JwtTokenUtils.getUserLoginDtoFromToken(headerToken);
|
||||
}
|
||||
String cookieToken =
|
||||
CookieUtils.getCookieValue(CookieUtils.getLoginTokenCookieKey(), request.getCookies());
|
||||
if (userDto == null && StrUtils.isNotNull(cookieToken)) {
|
||||
log.debug("analyse cookie toke :{}", cookieToken);
|
||||
userDto = JwtTokenUtils.getUserLoginDtoFromToken(cookieToken);
|
||||
}
|
||||
if (userDto != null) {
|
||||
SecurityContextHolder.getContext()
|
||||
.setAuthentication(JwtTokenUtils.getAuthentication(userDto));
|
||||
UserContextHolder.setContext(userDto);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录用户只有平台用户能使用: 已登录 且 平台用户 且 非内部调用
|
||||
//&& !StringUtils.equals(OrgConstants.ORG_TYPE_AGENT,userDto.getOrgType()) 开放代理机构登录中台判断条件
|
||||
if (!RequestUtils.FEIGN_REQUEST_HEADER_VALUE.equals(feignHeader)
|
||||
&& Boolean.TRUE.equals(securityProperties.getSuperiorOnly())
|
||||
&& userDto != null
|
||||
&& !OrgConstants.PLATFORM_ORG_ID.equals(userDto.getOrgId())) {
|
||||
ResponseUtils.printJson(response, AlitaResult.error("该用户不能使用系统"));
|
||||
return;
|
||||
}
|
||||
chain.doFilter(request, response);
|
||||
|
||||
} catch (ChangeTokenException cte) {
|
||||
ResponseUtils.printJson(response, cte.getResult());
|
||||
} catch (Throwable e) {
|
||||
throw e;
|
||||
} finally {
|
||||
// 清理上下文
|
||||
UserContextHolder.clearContext();
|
||||
HostContextHolder.clearHost();
|
||||
log.debug("clear context....");
|
||||
}
|
||||
}
|
||||
|
||||
private static void dealHostHeader(HttpServletRequest request) {
|
||||
if (HostContextHolder.getHost() != null) {
|
||||
return;
|
||||
}
|
||||
String hostHeader = request.getHeader(RequestUtils.ORIGIN_HOST_HEADER_KEY);
|
||||
if (StringUtils.isNoneBlank(hostHeader)) {
|
||||
HostContextHolder.setHost(hostHeader);
|
||||
log.debug("get request head origin_host:{}", hostHeader);
|
||||
} else {
|
||||
HostContextHolder.setHost(RequestUtils.getDomain(request));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
22
src/main/java/com/newyetai/security/jwt/JwtProperties.java
Normal file
22
src/main/java/com/newyetai/security/jwt/JwtProperties.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.newyetai.security.jwt;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* jwt属性
|
||||
* @author sr on 2019-12-21
|
||||
*/
|
||||
@Data
|
||||
public class JwtProperties {
|
||||
/** 是否启用 **/
|
||||
private Boolean enable;
|
||||
/** 加密密钥 **/
|
||||
private String secret;
|
||||
/** 过期时间(秒) **/
|
||||
private Long expiration;
|
||||
/** token对应request header的key **/
|
||||
private String header = "Authorization";
|
||||
/** token发行者 **/
|
||||
private String iss;
|
||||
|
||||
}
|
||||
344
src/main/java/com/newyetai/security/jwt/JwtTokenUtils.java
Normal file
344
src/main/java/com/newyetai/security/jwt/JwtTokenUtils.java
Normal file
@@ -0,0 +1,344 @@
|
||||
package com.newyetai.security.jwt;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.newyetai.common.dto.CasTokenDTO;
|
||||
import com.newyetai.common.exception.ChangeTokenException;
|
||||
import com.newyetai.common.util.EnvironmentUtils;
|
||||
import com.newyetai.common.util.JsonUtils;
|
||||
import com.newyetai.common.util.UserContextHolder;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.util.UserLoginUtils;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
import com.newyetai.usercenter.user.constant.UserTypeEnum;
|
||||
import com.newyetai.usercenter.user.dto.UserDetailQueryDTO;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-06-18
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class JwtTokenUtils {
|
||||
|
||||
/**
|
||||
* Authorization认证开头是"bearer "
|
||||
*/
|
||||
private static final String BEARER = "Bearer ";
|
||||
|
||||
/** 默认过期时间-秒 **/
|
||||
private static final Long DEFAULT_EXPIRATION = 3600 * 2L;
|
||||
|
||||
/** 默认密钥 **/
|
||||
private static final String DEFAULT_SECRET = "ca0f2602eacd4db79e92c8f1f7ff3d66";
|
||||
|
||||
/** 登录信息前缀 **/
|
||||
private static final String LOGIN_INFO_KEY_PREFIX = "login:info:uid:";
|
||||
private static final String TAG_KEY = "tag:";
|
||||
private static final String PRINCIPAL = "principal";
|
||||
|
||||
/** 默认 **/
|
||||
private static final int DEFAULT_TAG_LENGTH = 6;
|
||||
|
||||
/**
|
||||
* 过期时间
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-06-10
|
||||
*/
|
||||
public static Long getExpiration() {
|
||||
Object time = EnvironmentUtils.getProperty(SecurityConstants.PROP_KEY_JWT_EXPIRATION);
|
||||
try {
|
||||
return Long.parseLong((String) time);
|
||||
} catch (NumberFormatException e) {
|
||||
return DEFAULT_EXPIRATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 密钥
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-06-10
|
||||
*/
|
||||
public static String getSecret() {
|
||||
String secret = (String) EnvironmentUtils.getProperty(SecurityConstants.PROP_KEY_JWT_SECRET);
|
||||
return Strings.isNullOrEmpty(secret) ? DEFAULT_SECRET : secret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从数据声明生成令牌
|
||||
*
|
||||
* @param claims 数据声明
|
||||
* @return 令牌
|
||||
*/
|
||||
private static String generateTokenByClaims(Map<String, Object> claims) {
|
||||
Date expirationDate =
|
||||
new Date(System.currentTimeMillis() + getExpiration() * 1000);
|
||||
return Jwts.builder().setClaims(claims).setExpiration(expirationDate)
|
||||
.signWith(SignatureAlgorithm.HS512, getSecret().getBytes())
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取数据声明
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 数据声明
|
||||
*/
|
||||
private static Claims getClaimsFromToken(String token) {
|
||||
try {
|
||||
if (token.startsWith(BEARER)) {
|
||||
token = StringUtils.substring(token, BEARER.length());
|
||||
}
|
||||
return Jwts.parser().setSigningKey(getSecret().getBytes())
|
||||
.parseClaimsJws(token).getBody();
|
||||
} catch (SecurityException | MalformedJwtException e) {
|
||||
log.info("Invalid JWT signature.");
|
||||
log.trace("Invalid JWT signature trace: {}", e);
|
||||
} catch (ExpiredJwtException e) {
|
||||
Claims claims = e.getClaims();
|
||||
SecurityUserDTO securityUserDTO = JsonUtils.objToClassObj(claims.get(PRINCIPAL), SecurityUserDTO.class);
|
||||
if (UserTypeEnum.PERSON.getType().equals(securityUserDTO.getType())) {
|
||||
changeToken(securityUserDTO.getId());
|
||||
}
|
||||
log.info("Expired JWT token.");
|
||||
log.trace("Expired JWT token trace: {}", e);
|
||||
} catch (UnsupportedJwtException e) {
|
||||
log.info("Unsupported JWT token.");
|
||||
log.trace("Unsupported JWT token trace: {}", e);
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.info("JWT token compact of handler are invalid.");
|
||||
log.trace("JWT token compact of handler are invalid trace: {}", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据securityUserDetail登录用户对象生成token
|
||||
*
|
||||
* @param securityUserDTO
|
||||
* @return
|
||||
* @author sr on 2020-06-19
|
||||
*/
|
||||
public static String generateToken(SecurityUserDTO securityUserDTO) {
|
||||
try {
|
||||
if (securityUserDTO == null) {
|
||||
return null;
|
||||
}
|
||||
Map<String, Object> claims = Maps.newHashMap();
|
||||
claims.put("created", new Date());
|
||||
claims.put(PRINCIPAL, securityUserDTO);
|
||||
return generateTokenByClaims(claims);
|
||||
} catch (Exception e) {
|
||||
log.info("generateToken error : {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据登录用户生成token
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-06-19
|
||||
*/
|
||||
public String generateTokenByUserDetail(IUserDetail userDetail) {
|
||||
return generateToken((SecurityUserDTO) userDetail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前登录用户生成token
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-06-19
|
||||
*/
|
||||
public String generateTokenByCurrentLoginUser() {
|
||||
return this.generateTokenByUserDetail(UserContextHolder.getLoginUser());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据用户id获取用户信息生成token
|
||||
*
|
||||
* @param userId
|
||||
* @return
|
||||
* @author sr on 2020-06-19
|
||||
*/
|
||||
@Deprecated
|
||||
public String generateToken(Long userId) {
|
||||
return generateToken(UserDetailQueryDTO.builder().userId(userId).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据登录关键信息获取登录对象,生成token
|
||||
*
|
||||
* @param userDetailQueryDto
|
||||
* @return
|
||||
* @author sr on 2020-06-19
|
||||
*/
|
||||
public static String generateToken(UserDetailQueryDTO userDetailQueryDto) {
|
||||
return generateToken(UserLoginUtils.loadUserToMock(userDetailQueryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换token(个人)
|
||||
* @param userId
|
||||
* @throws ChangeTokenException
|
||||
* @author sr on 2021-08-10
|
||||
*/
|
||||
public static void changeToken(Long userId)
|
||||
throws ChangeTokenException {
|
||||
changeToken(userId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换token(企业)
|
||||
* @param userDetailQueryDto
|
||||
* @throws ChangeTokenException
|
||||
* @author sr on 2021-07-07
|
||||
*/
|
||||
public static void changeToken(Long userId, Long orgId)
|
||||
throws ChangeTokenException {
|
||||
String token = generateToken(UserDetailQueryDTO.builder().userId(userId).orgId(orgId).build());
|
||||
throw new ChangeTokenException(CasTokenDTO.builder().userId(userId).token(token).build());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据token解析登录用户对象信息
|
||||
*
|
||||
* @param token
|
||||
* @return securityUserDetail
|
||||
* @author sr on 2020-06-19
|
||||
*/
|
||||
public static SecurityUserDTO getUserLoginDtoFromToken(String token) {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
if (claims == null) {
|
||||
return null;
|
||||
}
|
||||
return JsonUtils.objToClassObj(claims.get(PRINCIPAL), SecurityUserDTO.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取认证信息
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
*/
|
||||
public static Authentication getAuthentication(IUserDetail securityUserDTO) {
|
||||
try {
|
||||
if (securityUserDTO == null) {
|
||||
return null;
|
||||
}
|
||||
// TODO 凭证暂为null
|
||||
Object credentials = null;
|
||||
return new UsernamePasswordAuthenticationToken(securityUserDTO,
|
||||
credentials == null ? "N/A" : credentials, null);
|
||||
} catch (Exception e) {
|
||||
log.info("getAuthenticationFromToken error : {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户
|
||||
*
|
||||
* @param auth
|
||||
* @return
|
||||
* @author sr on 2020-06-16
|
||||
*/
|
||||
public static IUserDetail getLoginUser(Authentication auth) {
|
||||
Object principal = auth == null ? null : auth.getPrincipal();
|
||||
if (principal != null && principal instanceof IUserDetail) {
|
||||
try {
|
||||
return (IUserDetail) principal;
|
||||
} catch (Exception e) {
|
||||
log.info("context user not userDetail...");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从令牌中获取账号
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 账号
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
String username;
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
username = claims.getSubject();
|
||||
} catch (Exception e) {
|
||||
username = null;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断令牌是否过期
|
||||
*
|
||||
* @param token 令牌
|
||||
* @return 是否过期
|
||||
*/
|
||||
public Boolean isTokenExpired(String token) {
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
Date expiration = claims.getExpiration();
|
||||
return expiration.before(new Date());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*
|
||||
* @param token 原令牌
|
||||
* @return 新令牌
|
||||
*/
|
||||
public String refreshToken(String token) {
|
||||
String refreshedToken;
|
||||
try {
|
||||
Claims claims = getClaimsFromToken(token);
|
||||
claims.put("created", new Date());
|
||||
refreshedToken = generateTokenByClaims(claims);
|
||||
} catch (Exception e) {
|
||||
refreshedToken = null;
|
||||
}
|
||||
return refreshedToken;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取登录Redis存储的key key=login:uid:${userId}
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param tag 唯一标示
|
||||
* @return
|
||||
*/
|
||||
public String getLoginInfoRedisKey(Long userId, String tag) {
|
||||
log.info("------getLoginRedisKey userId: {}, tag: {}", userId, tag);
|
||||
return LOGIN_INFO_KEY_PREFIX + userId + TAG_KEY + tag;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.newyetai.security.mapstruct;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import com.newyetai.usercenter.auth.dto.AuthDetailLoginDTO;
|
||||
import com.newyetai.usercenter.auth.wechat.dto.WechatAuthUserInfoDTO;
|
||||
|
||||
@Mapper
|
||||
public interface AuthDetailMapMapper{
|
||||
|
||||
AuthDetailMapMapper INSTANCE = Mappers.getMapper(AuthDetailMapMapper.class);
|
||||
|
||||
@Mappings(value = {
|
||||
@Mapping(target = "avatar", source = "headimgurl"),
|
||||
@Mapping(target = "gender", source = "sex"),
|
||||
@Mapping(target = "uniqueId", source = "unionid"),
|
||||
})
|
||||
AuthDetailLoginDTO toDto(WechatAuthUserInfoDTO wechatDto);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.newyetai.security.mapstruct;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-06-08
|
||||
*/
|
||||
@Mapper
|
||||
public interface UserMapMapper {
|
||||
|
||||
UserMapMapper INSTANCE = Mappers.getMapper(UserMapMapper.class);
|
||||
|
||||
/**
|
||||
* to dto
|
||||
*
|
||||
* @param userDto
|
||||
* @return
|
||||
* @author sr on 2020-06-08
|
||||
*/
|
||||
@Mapping(target = "tag", expression = "java(com.newyetai.common.util.RandomUtils.getRandomString(6))")
|
||||
SecurityUserDTO dtoToDto(UserDetailDTO userDto);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.newyetai.security.mobile;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
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 com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginPhoneDTO;
|
||||
|
||||
/**
|
||||
* 手机号认证过滤器,指定请求组装token,调起认证
|
||||
*
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
public class MobileAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
private boolean postOnly = true;
|
||||
|
||||
public static final String REQUEST_METHOD = "POST";
|
||||
public static final AntPathRequestMatcher requiresAuthenticationRequestMatcher =
|
||||
new AntPathRequestMatcher("/login_phone", REQUEST_METHOD);
|
||||
|
||||
public MobileAuthenticationFilter() {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
||||
|
||||
if (postOnly && !REQUEST_METHOD.equals(request.getMethod())) {
|
||||
throw new AuthenticationServiceException(
|
||||
"Authentication method not supported: " + request.getMethod());
|
||||
}
|
||||
|
||||
String userTypeStr = request.getParameter(SecurityConstants.LOGIN_USER_TYPE_KEY);
|
||||
Assert.isTrue(StringUtils.isNotBlank(userTypeStr), new BadLoginException("用户类型不能为空"));
|
||||
Integer userType = null;
|
||||
try {
|
||||
userType = Integer.valueOf(userTypeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadLoginException("用户类型错误:" + userTypeStr);
|
||||
}
|
||||
|
||||
String phone = request.getParameter(SecurityConstants.LOGIN_PHONE_KEY);
|
||||
String code = request.getParameter(SecurityConstants.LOGIN_CODE_KEY);
|
||||
String registerCode = request.getParameter(SecurityConstants.LOGIN_REGISTER_CODE_KEY);
|
||||
String entrypoint = request.getParameter(SecurityConstants.LOGIN_ENTRYPOINT_KEY);
|
||||
phone = phone == null ? "" : phone.trim();
|
||||
code = code == null ? "" : code.trim();
|
||||
|
||||
AbstractAuthenticationToken authRequest = new MobileAuthenticationToken(
|
||||
LoginPhoneDTO.builder().phone(phone).code(code).userType(userType)
|
||||
.registerCode(registerCode).entrypoint(entrypoint).build(),
|
||||
code);
|
||||
|
||||
// Allow subclasses to set the "details" property
|
||||
setDetails(request, authRequest);
|
||||
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
|
||||
protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
|
||||
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.newyetai.security.mobile;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.validation.util.ValidateUtils;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.security.mapstruct.UserMapMapper;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginPhoneDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
/**
|
||||
* 手机号认证provider,比对信息是否匹配
|
||||
*
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
public class MobileAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public MobileAuthenticationProvider(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
LoginPhoneDTO loginPhoneDTO = (LoginPhoneDTO) authentication.getPrincipal();
|
||||
String errMsg = ValidateUtils.validate(loginPhoneDTO);
|
||||
Assert.isTrue(errMsg == null, new BadLoginException(errMsg));
|
||||
SecurityUserDTO loadedUser = null;
|
||||
try {
|
||||
UserDetailDTO userDetailDTO = this.userLoginService.loadUser(loginPhoneDTO);
|
||||
loadedUser = UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
|
||||
} catch (BadLoginException be) {
|
||||
throw be;
|
||||
} catch (Exception e) {
|
||||
throw new BadLoginException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
MobileAuthenticationToken result =
|
||||
new MobileAuthenticationToken(loadedUser, listUserGrantedAuthorities(loadedUser));
|
||||
result.setDetails(authentication.getDetails());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return (MobileAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
private Set<GrantedAuthority> listUserGrantedAuthorities(UserDetails userDetails) {
|
||||
// 什么都不用做,访问资源鉴权的时会拿userDetail去缓存获取对应权限
|
||||
return new HashSet<GrantedAuthority>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.mobile;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
/**
|
||||
* 手机号认证信息
|
||||
*
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
public class MobileAuthenticationToken extends UsernamePasswordAuthenticationToken {
|
||||
|
||||
public MobileAuthenticationToken(Object principal, Object credentials) {
|
||||
super(principal, credentials);
|
||||
}
|
||||
|
||||
public MobileAuthenticationToken(Object principal, Object credentials,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(principal, credentials, authorities);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.newyetai.security.service;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
|
||||
/**
|
||||
* 主要提供用户的授权信息的收集功能
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
public interface GrantedAuthorityService {
|
||||
|
||||
/**
|
||||
* 获取用户的授权信息
|
||||
*
|
||||
* @param userDetails 用户信息
|
||||
* @return 权限信息
|
||||
*/
|
||||
Collection<? extends GrantedAuthority> getGrantedAuthorities(IUserDetail userDetails);
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.newyetai.security.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.usercenter.authority.dto.PermissionQueryDTO;
|
||||
import com.newyetai.usercenter.authority.dto.UrlPermissionDTO;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-09-07
|
||||
*/
|
||||
public interface IPermissionSecurityService {
|
||||
|
||||
@ApiOperation("查询角色权限分配菜单接口")
|
||||
@GetMapping("/permission/list_url")
|
||||
AlitaResult<List<UrlPermissionDTO>> listUrl(PermissionQueryDTO permissionQueryDTO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.newyetai.security.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
import com.newyetai.usercenter.resource.dto.ComponentDTO;
|
||||
import com.newyetai.usercenter.resource.dto.UrlDTO;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-09-08
|
||||
*/
|
||||
public interface IResourceSecurityService {
|
||||
|
||||
/**
|
||||
* 查询当前登录用户资源树状列表(包含菜单组件)
|
||||
* @param systemCode
|
||||
* @return
|
||||
* @author sr on 2019-10-15
|
||||
*/
|
||||
List<UrlDTO> listResource(Integer systemCode, IUserDetail iUserDetail);
|
||||
|
||||
/**
|
||||
* 获取当前登录用户指定角色的资源树状列表(包含菜单组件)
|
||||
* @param systemCode
|
||||
* @param roleId
|
||||
* @return
|
||||
* @author sr on 2019-10-16
|
||||
*/
|
||||
List<UrlDTO> listResource(Integer systemCode, IUserDetail iUserDetail, Long roleId);
|
||||
|
||||
/**
|
||||
* 根据应用+机构获取url资源
|
||||
*
|
||||
* @param systemCode
|
||||
* @param orgId
|
||||
* @return
|
||||
* @author sr on 2020-09-08
|
||||
*/
|
||||
List<UrlDTO> listUrlDTO(Integer systemCode, Long orgId);
|
||||
|
||||
/**
|
||||
* 根据应用+机构获取Component资源
|
||||
*
|
||||
* @param systemCode
|
||||
* @param orgId
|
||||
* @return
|
||||
* @author sr on 2020-09-08
|
||||
*/
|
||||
List<ComponentDTO> listComponentDTO(Integer systemCode, Long orgId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.newyetai.security.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.usercenter.authority.dto.RoleAllocationDTO;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-09-07
|
||||
*/
|
||||
public interface IRoleAllocationSecurityService {
|
||||
|
||||
@ApiOperation("查询用户分配的角色")
|
||||
@GetMapping("/role/findByUserId/{userId}")
|
||||
AlitaResult<List<RoleAllocationDTO>> findByUserId(@PathVariable("userId") Long userId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.newyetai.security.service;
|
||||
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.usercenter.user.dto.UserDetailQueryDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-09-07
|
||||
*/
|
||||
public interface IUserSecurityService {
|
||||
|
||||
@ApiOperation("根据登录信息获取用户信息")
|
||||
@PostMapping("/anon/user/find_user_detail_dto")
|
||||
AlitaResult<UserDetailDTO> findUserDetailDTOResult(UserDetailQueryDTO userDetailQueryDto);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.newyetai.security.service.impl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.newyetai.security.access.provider.GrantedAuthorityProvider;
|
||||
import com.newyetai.security.service.GrantedAuthorityService;
|
||||
import com.newyetai.usercenter.dto.IUserDetail;
|
||||
|
||||
/**
|
||||
* 主要提供用户的授权信息的收集功能
|
||||
*
|
||||
* @author on 2019-11-05
|
||||
*/
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
public class GrantedAuthorityServiceImpl implements GrantedAuthorityService, InitializingBean {
|
||||
|
||||
@Autowired
|
||||
private List<GrantedAuthorityProvider> providers;
|
||||
|
||||
/**
|
||||
* 获取用户的授权信息
|
||||
*
|
||||
* @param userDetails 用户信息
|
||||
* @return 权限信息
|
||||
*/
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getGrantedAuthorities(IUserDetail userDetails) {
|
||||
Collection<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
|
||||
for (GrantedAuthorityProvider provider : providers) {
|
||||
Collection<? extends GrantedAuthority> list = provider.provide(userDetails);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
grantedAuthorities.addAll(list);
|
||||
}
|
||||
}
|
||||
return grantedAuthorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
Assert.notEmpty(providers, "providers can not be empty");
|
||||
AnnotationAwareOrderComparator.sort(providers);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.newyetai.security.social.service;
|
||||
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
|
||||
import com.newyetai.security.ssb.SsbAuthenticationFilter;
|
||||
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
|
||||
|
||||
public enum CommonOAuth2Provider {
|
||||
|
||||
WECHAT {
|
||||
|
||||
@Override
|
||||
public Builder getBuilder(String registrationId) {
|
||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
|
||||
builder.scope("snsapi_userinfo");
|
||||
builder.authorizationUri("https://open.weixin.qq.com/connect/oauth2/authorize");
|
||||
builder.tokenUri("https://api.weixin.qq.com/sns/oauth2/access_token");
|
||||
builder.userInfoUri("https://api.weixin.qq.com/sns/userinfo");
|
||||
builder.userNameAttributeName("openid");
|
||||
builder.clientName(AuthTypeEnum.WECHAT.name());
|
||||
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
return builder;
|
||||
}
|
||||
},
|
||||
|
||||
SSBAPP {
|
||||
|
||||
@Override
|
||||
public Builder getBuilder(String registrationId) {
|
||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
|
||||
builder.authorizationUri("https://api.eshimin.com/newOauth/authorize");
|
||||
builder.tokenUri("https://api.eshimin.com/newOauth/accessToken");
|
||||
builder.userInfoUri("https://api.eshimin.com/newOauth/userInfo");
|
||||
builder.clientName(AuthTypeEnum.SSB_APP.name());
|
||||
builder.scope("read");
|
||||
builder.redirectUriTemplate("{baseUrl}/" + SsbAuthenticationFilter.LOGIN_URI);
|
||||
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
return builder;
|
||||
}
|
||||
},
|
||||
|
||||
WECHAT_LITE {
|
||||
|
||||
@Override
|
||||
public Builder getBuilder(String registrationId) {
|
||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
|
||||
builder.clientName(AuthTypeEnum.WECHAT_LITE.name());
|
||||
return builder;
|
||||
}
|
||||
},
|
||||
|
||||
ALIPAY {
|
||||
|
||||
@Override
|
||||
public Builder getBuilder(String registrationId) {
|
||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||
ClientAuthenticationMethod.POST, DEFAULT_REDIRECT_URL);
|
||||
builder.clientName(AuthTypeEnum.ALIPAY.name());
|
||||
return builder;
|
||||
}
|
||||
},
|
||||
|
||||
ALIPAY_LITE {
|
||||
|
||||
@Override
|
||||
public Builder getBuilder(String registrationId) {
|
||||
ClientRegistration.Builder builder = getBuilder(registrationId,
|
||||
ClientAuthenticationMethod.BASIC, DEFAULT_REDIRECT_URL);
|
||||
builder.clientName(AuthTypeEnum.ALIPAY_LITE.name());
|
||||
return builder;
|
||||
}
|
||||
},
|
||||
|
||||
;
|
||||
|
||||
|
||||
private static final String DEFAULT_REDIRECT_URL =
|
||||
"{baseUrl}/{action}/oauth2/code/{registrationId}";
|
||||
|
||||
protected final ClientRegistration.Builder getBuilder(String registrationId,
|
||||
ClientAuthenticationMethod method, String redirectUri) {
|
||||
ClientRegistration.Builder builder = ClientRegistration.withRegistrationId(registrationId);
|
||||
builder.clientAuthenticationMethod(method);
|
||||
builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
|
||||
builder.redirectUriTemplate(redirectUri);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new
|
||||
* {@link org.springframework.security.oauth2.client.registration.ClientRegistration.Builder
|
||||
* ClientRegistration.Builder} pre-configured with provider defaults.
|
||||
* @param registrationId the registration-id used with the new builder
|
||||
* @return a builder instance
|
||||
*/
|
||||
public abstract ClientRegistration.Builder getBuilder(String registrationId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.newyetai.security.social.service;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
|
||||
public interface IOauthProvider {
|
||||
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* 修改默认auth的请求地址
|
||||
*
|
||||
* @param request
|
||||
* @param authorizationRequest
|
||||
* @return
|
||||
* @author sr on 2021-06-28
|
||||
*/
|
||||
default OAuth2AuthorizationRequest decorateRequest(HttpServletRequest request,
|
||||
OAuth2AuthorizationRequest authorizationRequest) {
|
||||
return authorizationRequest;
|
||||
}
|
||||
|
||||
default OAuth2AccessTokenResponse getTokenResponse(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取token转换器(无则OAuth2AuthorizationCodeGrantRequestEntityConverter)
|
||||
* @return
|
||||
* @author sr on 2021-06-28
|
||||
*/
|
||||
default Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> getTokenConvert(){
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户转换器(无则OAuth2UserRequestEntityConverter)
|
||||
* @return
|
||||
* @author sr on 2021-06-28
|
||||
*/
|
||||
default Converter<OAuth2UserRequest, RequestEntity<?>> getUserConvert(){
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 处理第三方用户信息,获取登录用户
|
||||
* @param userRequest
|
||||
* @param userJsonStr
|
||||
* @return
|
||||
* @author sr on 2021-06-28
|
||||
*/
|
||||
default OAuth2User dealUser(OAuth2UserRequest userRequest, String userJsonStr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.newyetai.security.social.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.convert.ApplicationConversionService;
|
||||
import org.springframework.core.convert.ConversionException;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.Builder;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.util.JsonUtils;
|
||||
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
|
||||
import com.newyetai.usercenter.auth.dto.AuthClientDTO;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class OAuth2ClientRegistrationAdapter {
|
||||
|
||||
private static Map<String, CommonOAuth2Provider> providers = Maps.newConcurrentMap();
|
||||
|
||||
private OAuth2ClientRegistrationAdapter() {}
|
||||
|
||||
public static ClientRegistration getClientRegistration(String registrationId,
|
||||
AuthTypeEnum authTypeEnum,
|
||||
AuthClientDTO authClientDTO) {
|
||||
Assert.isTrue(authTypeEnum != null, new IllegalArgumentException("非法授权标识"));
|
||||
OAuth2ClientProperties.Registration registration =
|
||||
JsonUtils.toObject(authClientDTO.getParamData(), OAuth2ClientProperties.Registration.class);
|
||||
return getClientRegistration(registrationId, authTypeEnum, registration);
|
||||
}
|
||||
|
||||
private static ClientRegistration getClientRegistration(String registrationId,
|
||||
AuthTypeEnum authTypeEnum,
|
||||
OAuth2ClientProperties.Registration registration) {
|
||||
Assert.isTrue(authTypeEnum != null, new IllegalArgumentException("非法授权标识"));
|
||||
Builder builder = getBuilder(authTypeEnum.name()).getBuilder(registrationId);
|
||||
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
|
||||
map.from(registration::getClientId).to(builder::clientId);
|
||||
map.from(registration::getClientSecret).to(builder::clientSecret);
|
||||
map.from(registration::getClientAuthenticationMethod).as(ClientAuthenticationMethod::new)
|
||||
.to(builder::clientAuthenticationMethod);
|
||||
map.from(registration::getAuthorizationGrantType).as(AuthorizationGrantType::new)
|
||||
.to(builder::authorizationGrantType);
|
||||
map.from(registration::getRedirectUri).to(builder::redirectUriTemplate);
|
||||
map.from(registration::getScope).as((scope) -> StringUtils.toStringArray(scope))
|
||||
.to(builder::scope);
|
||||
map.from(registration::getClientName).to(builder::clientName);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static CommonOAuth2Provider getBuilder(String authTypeName) {
|
||||
CommonOAuth2Provider provider = providers.get(authTypeName);
|
||||
if (provider == null) {
|
||||
try {
|
||||
provider =
|
||||
ApplicationConversionService.getSharedInstance().convert(authTypeName,
|
||||
CommonOAuth2Provider.class);
|
||||
providers.put(authTypeName, provider);
|
||||
} catch (ConversionException ex) {
|
||||
log.error("授权类型标识错误,未找到对应的oauth2提供者:{}", authTypeName);
|
||||
throw new IllegalArgumentException("授权类型标识错误,未找到对应的oauth2提供者:" + authTypeName, ex);
|
||||
}
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.newyetai.security.social.service;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter;
|
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.newyetai.security.social.util.Oauth2ProviderUtils;
|
||||
|
||||
/**
|
||||
* code获取token
|
||||
*
|
||||
* @author sr on 2021-01-12
|
||||
*/
|
||||
public class StandardAuthorizationCodeTokenResponseClient
|
||||
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
|
||||
public static final String INVALID_TOKEN_RESPONSE_ERROR_CODE = "invalid_token_response";
|
||||
public static final RestOperations restOperations;
|
||||
|
||||
private Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter =
|
||||
new OAuth2AuthorizationCodeGrantRequestEntityConverter();
|
||||
|
||||
static {
|
||||
RestTemplate restTemplate = new RestTemplate(Arrays.asList(
|
||||
new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter(),
|
||||
new StringHttpMessageConverter()));
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
restOperations = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
|
||||
Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null");
|
||||
|
||||
// 不同类型不同请求体
|
||||
OAuth2AccessTokenResponse tokenResponse = Oauth2ProviderUtils
|
||||
.getProvider(authorizationCodeGrantRequest.getClientRegistration().getClientName())
|
||||
.getTokenResponse(authorizationCodeGrantRequest);
|
||||
|
||||
if (tokenResponse != null) {
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
RequestEntity<?> request = this.requestEntityConverter.convert(authorizationCodeGrantRequest);
|
||||
|
||||
ResponseEntity<OAuth2AccessTokenResponse> response;
|
||||
try {
|
||||
response = restOperations.exchange(request, OAuth2AccessTokenResponse.class);
|
||||
} catch (RestClientException ex) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
|
||||
+ ex.getMessage(),
|
||||
null);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
|
||||
tokenResponse = response.getBody();
|
||||
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
|
||||
// As per spec, in Section 5.1 Successful Access Token Response
|
||||
// https://tools.ietf.org/html/rfc6749#section-5.1
|
||||
// If AccessTokenResponse.scope is empty, then default to the scope
|
||||
// originally requested by the client in the Token Request
|
||||
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
|
||||
.scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes())
|
||||
.build();
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Converter} used for converting the {@link OAuth2AuthorizationCodeGrantRequest}
|
||||
* to a {@link RequestEntity} representation of the OAuth 2.0 Access Token Request.
|
||||
*
|
||||
* @param requestEntityConverter the {@link Converter} used for converting to a {@link RequestEntity} representation of the Access Token Request
|
||||
*/
|
||||
public void setRequestEntityConverter(
|
||||
Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> requestEntityConverter) {
|
||||
Assert.notNull(requestEntityConverter, "requestEntityConverter cannot be null");
|
||||
this.requestEntityConverter = requestEntityConverter;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.newyetai.security.social.service;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
|
||||
import com.newyetai.usercenter.auth.dto.AuthClientDTO;
|
||||
import com.newyetai.usercenter.auth.dto.AuthClientQueryDTO;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
public class StandardClientRegistrationRepository implements ClientRegistrationRepository {
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public StandardClientRegistrationRepository(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientRegistration findByRegistrationId(String registrationId) {
|
||||
Assert.isTrue(StringUtils.isNotBlank(registrationId),
|
||||
new IllegalArgumentException("oauth授权注册标识不能为空"));
|
||||
AuthClientDTO authClientDTO = userLoginService
|
||||
.findAuthClientDTO(AuthClientQueryDTO.builder().id(Long.valueOf(registrationId)).build());
|
||||
AuthTypeEnum authTypeEnum = AuthTypeEnum.getByCode(authClientDTO.getAuthTypeCode());
|
||||
return OAuth2ClientRegistrationAdapter.getClientRegistration(authClientDTO.getId().toString(),
|
||||
authTypeEnum, authClientDTO);
|
||||
}
|
||||
|
||||
public ClientRegistration findByRegistrationId(String authTypeName, String domainOrIp) {
|
||||
AuthTypeEnum authTypeEnum = AuthTypeEnum.valueOf(authTypeName.toUpperCase());
|
||||
return this.findByRegistrationId(authTypeEnum, domainOrIp);
|
||||
}
|
||||
|
||||
public ClientRegistration findByRegistrationId(Integer authTypeCode, String domainOrIp) {
|
||||
AuthTypeEnum authTypeEnum = AuthTypeEnum.getByCode(authTypeCode);
|
||||
return this.findByRegistrationId(authTypeEnum, domainOrIp);
|
||||
}
|
||||
|
||||
private ClientRegistration findByRegistrationId(AuthTypeEnum authTypeEnum, String domainOrIp) {
|
||||
AuthClientDTO authClientDTO = userLoginService.findAuthClientDTO(
|
||||
AuthClientQueryDTO.builder().domainOrIp(domainOrIp).authTypeCode(authTypeEnum.getCode())
|
||||
.build());
|
||||
return OAuth2ClientRegistrationAdapter.getClientRegistration(authClientDTO.getId().toString(),
|
||||
authTypeEnum, authClientDTO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.newyetai.security.social.service;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequestEntityConverter;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import com.newyetai.security.social.util.Oauth2ProviderUtils;
|
||||
|
||||
public class StandardOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
|
||||
|
||||
private static final String MISSING_USER_INFO_URI_ERROR_CODE = "missing_user_info_uri";
|
||||
|
||||
private static final String MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE =
|
||||
"missing_user_name_attribute";
|
||||
|
||||
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
|
||||
|
||||
private Converter<OAuth2UserRequest, RequestEntity<?>> requestEntityConverter =
|
||||
new OAuth2UserRequestEntityConverter();
|
||||
|
||||
private RestOperations restOperations;
|
||||
|
||||
public StandardOAuth2UserService() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
|
||||
this.restOperations = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
Assert.notNull(userRequest, "userRequest cannot be null");
|
||||
|
||||
if (!StringUtils.hasText(
|
||||
userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,
|
||||
"Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
|
||||
+ userRequest.getClientRegistration().getRegistrationId(),
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
|
||||
.getUserInfoEndpoint().getUserNameAttributeName();
|
||||
if (!StringUtils.hasText(userNameAttributeName)) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,
|
||||
"Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "
|
||||
+ userRequest.getClientRegistration().getRegistrationId(),
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||
}
|
||||
|
||||
IOauthProvider provider = Oauth2ProviderUtils
|
||||
.getProvider(userRequest.getClientRegistration().getClientName());
|
||||
// 不同类型不同请求体
|
||||
Converter<OAuth2UserRequest, RequestEntity<?>> converter =
|
||||
provider.getUserConvert();
|
||||
if (converter == null) {
|
||||
converter = requestEntityConverter;
|
||||
}
|
||||
|
||||
// 获得request
|
||||
RequestEntity<?> request = converter.convert(userRequest);
|
||||
|
||||
ResponseEntity<String> response;
|
||||
try {
|
||||
// 执行request
|
||||
response = this.restOperations.exchange(request, String.class);
|
||||
} catch (OAuth2AuthorizationException ex) {
|
||||
OAuth2Error oauth2Error = ex.getError();
|
||||
StringBuilder errorDetails = new StringBuilder();
|
||||
errorDetails.append("Error details: [");
|
||||
errorDetails.append("UserInfo Uri: ").append(
|
||||
userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
|
||||
errorDetails.append(", Error Code: ").append(oauth2Error.getErrorCode());
|
||||
if (oauth2Error.getDescription() != null) {
|
||||
errorDetails.append(", Error Description: ").append(oauth2Error.getDescription());
|
||||
}
|
||||
errorDetails.append("]");
|
||||
oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
|
||||
"An error occurred while attempting to retrieve the UserInfo Resource: "
|
||||
+ errorDetails.toString(),
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
|
||||
} catch (RestClientException ex) {
|
||||
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
|
||||
"An error occurred while attempting to retrieve the UserInfo Resource: "
|
||||
+ ex.getMessage(),
|
||||
null);
|
||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
|
||||
}
|
||||
|
||||
// 解析response
|
||||
String userAttributes = response.getBody();
|
||||
return provider.dealUser(userRequest, userAttributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package com.newyetai.security.social.service;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.web.util.UrlUtils;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.newyetai.common.util.RequestUtils;
|
||||
import com.newyetai.security.social.util.Oauth2ProviderUtils;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.user.constant.UserTypeEnum;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2AuthorizationRequestResolver} that attempts to
|
||||
* resolve an {@link OAuth2AuthorizationRequest} from the provided {@code HttpServletRequest}
|
||||
* using the default request {@code URI} pattern {@code /oauth2/authorization/{registrationId}}.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The default base {@code URI} {@code /oauth2/authorization} may be overridden
|
||||
* via it's constructor {@link #DefaultOAuth2AuthorizationRequestResolver(ClientRegistrationRepository, String)}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Rob Winch
|
||||
* @since 5.1
|
||||
* @see OAuth2AuthorizationRequestResolver
|
||||
* @see OAuth2AuthorizationRequestRedirectFilter
|
||||
*/
|
||||
public final class StandardOauth2AuthorizationRequestResolver
|
||||
implements OAuth2AuthorizationRequestResolver {
|
||||
private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId";
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
private final AntPathRequestMatcher authorizationRequestMatcher;
|
||||
private final StringKeyGenerator stateGenerator =
|
||||
new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
||||
|
||||
/**
|
||||
* Constructs a {@code DefaultOAuth2AuthorizationRequestResolver} using the provided parameters.
|
||||
*
|
||||
* @param clientRegistrationRepository the repository of client registrations
|
||||
* @param authorizationRequestBaseUri the base {@code URI} used for resolving authorization requests
|
||||
*/
|
||||
public StandardOauth2AuthorizationRequestResolver(
|
||||
// StandardClientRegistrationRepository standardClientRegistrationRepository,
|
||||
ClientRegistrationRepository standardClientRegistrationRepository,
|
||||
String authorizationRequestBaseUri) {
|
||||
Assert.notNull(standardClientRegistrationRepository,
|
||||
"clientRegistrationRepository cannot be null");
|
||||
Assert.hasText(authorizationRequestBaseUri, "authorizationRequestBaseUri cannot be empty");
|
||||
this.clientRegistrationRepository = standardClientRegistrationRepository;
|
||||
this.authorizationRequestMatcher = new AntPathRequestMatcher(
|
||||
authorizationRequestBaseUri + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
|
||||
String registrationId = this.resolveRegistrationId(request);
|
||||
String redirectUriAction = getAction(request, "login");
|
||||
return resolve(request, registrationId, redirectUriAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) {
|
||||
if (registrationId == null) {
|
||||
return null;
|
||||
}
|
||||
String redirectUriAction = getAction(request, "authorize");
|
||||
return resolve(request, registrationId, redirectUriAction);
|
||||
}
|
||||
|
||||
private String getAction(HttpServletRequest request, String defaultAction) {
|
||||
String action = request.getParameter("action");
|
||||
if (action == null) {
|
||||
return defaultAction;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId,
|
||||
String redirectUriAction) {
|
||||
if (registrationId == null) {
|
||||
return null;
|
||||
}
|
||||
String domainOrIp = RequestUtils.getDomain(request);
|
||||
ClientRegistration clientRegistration = null;
|
||||
if (clientRegistrationRepository instanceof StandardClientRegistrationRepository) {
|
||||
clientRegistration =
|
||||
((StandardClientRegistrationRepository) this.clientRegistrationRepository)
|
||||
.findByRegistrationId(registrationId, domainOrIp);
|
||||
} else {
|
||||
clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
|
||||
}
|
||||
|
||||
if (clientRegistration == null) {
|
||||
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest.Builder builder;
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE
|
||||
.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
builder = OAuth2AuthorizationRequest.authorizationCode();
|
||||
} else if (AuthorizationGrantType.IMPLICIT
|
||||
.equals(clientRegistration.getAuthorizationGrantType())) {
|
||||
builder = OAuth2AuthorizationRequest.implicit();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid Authorization Grant Type (" +
|
||||
clientRegistration.getAuthorizationGrantType().getValue() +
|
||||
") for Client Registration with Id: " + clientRegistration.getRegistrationId());
|
||||
}
|
||||
|
||||
String redirectUriStr = this.expandRedirectUri(request, clientRegistration, redirectUriAction);
|
||||
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put(OAuth2ParameterNames.REGISTRATION_ID,
|
||||
clientRegistration.getRegistrationId());
|
||||
String userType = request.getParameter(SecurityConstants.LOGIN_USER_TYPE_KEY);
|
||||
String registerCode = request.getParameter(SecurityConstants.LOGIN_REGISTER_CODE_KEY);
|
||||
String entrypoint = request.getParameter(SecurityConstants.LOGIN_ENTRYPOINT_KEY);
|
||||
if (StringUtils.isBlank(userType)) {
|
||||
userType = UserTypeEnum.PERSON.getType().toString();
|
||||
}
|
||||
additionalParameters.put(SecurityConstants.LOGIN_USER_TYPE_KEY, userType);
|
||||
if (StringUtils.isNotBlank(registerCode)) {
|
||||
additionalParameters.put(SecurityConstants.LOGIN_REGISTER_CODE_KEY, registerCode);
|
||||
}
|
||||
if (StringUtils.isNotBlank(entrypoint)) {
|
||||
additionalParameters.put(SecurityConstants.LOGIN_ENTRYPOINT_KEY, entrypoint);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = builder
|
||||
.clientId(clientRegistration.getClientId())
|
||||
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
|
||||
.redirectUri(redirectUriStr)
|
||||
.scopes(clientRegistration.getScopes())
|
||||
.state(this.stateGenerator.generateKey())
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
// 不同授权需要额外修饰
|
||||
IOauthProvider oauthProvider = Oauth2ProviderUtils.getProvider(registrationId);
|
||||
if (oauthProvider != null) {
|
||||
return oauthProvider.decorateRequest(request, authorizationRequest);
|
||||
}
|
||||
return authorizationRequest;
|
||||
}
|
||||
|
||||
private String resolveRegistrationId(HttpServletRequest request) {
|
||||
if (this.authorizationRequestMatcher.matches(request)) {
|
||||
return this.authorizationRequestMatcher
|
||||
.extractUriTemplateVariables(request).get(REGISTRATION_ID_URI_VARIABLE_NAME);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String expandRedirectUri(HttpServletRequest request,
|
||||
ClientRegistration clientRegistration, String action) {
|
||||
// Supported URI variables -> baseUrl, action, registrationId
|
||||
// Used in -> CommonOAuth2Provider.DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"
|
||||
Map<String, String> uriVariables = new HashMap<>();
|
||||
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
|
||||
String baseUrl = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replaceQuery(null)
|
||||
.replacePath(request.getContextPath())
|
||||
.build()
|
||||
.toUriString();
|
||||
uriVariables.put("baseUrl", baseUrl);
|
||||
if (action != null) {
|
||||
uriVariables.put("action", action);
|
||||
}
|
||||
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate())
|
||||
.buildAndExpand(uriVariables)
|
||||
.toUriString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.newyetai.security.social.ssb.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.newyetai.sdk.ssb.SsbProperties;
|
||||
import com.newyetai.sdk.ssb.util.SM2Utils;
|
||||
import com.newyetai.security.social.service.IOauthProvider;
|
||||
import com.newyetai.security.util.SecurityUtils;
|
||||
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SsbOauthLoginProvider implements IOauthProvider {
|
||||
|
||||
private static final String AUTH_URL =
|
||||
"https://api.eshimin.com/newOauth/authorize?client_id=%s&response_type=authorization_code&scope=read&redirect_uri=%s×tamp=%s&sign=%s&state=%s";
|
||||
|
||||
@Autowired
|
||||
private SsbProperties ssbProperties;
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return AuthTypeEnum.SSB_APP.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest decorateRequest(HttpServletRequest request,
|
||||
OAuth2AuthorizationRequest authorizationRequest) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String sign = SM2Utils.sign(ssbProperties.getSm2PrivateKey(),
|
||||
ssbProperties.getAppKey() + ssbProperties.getAppSecret() + timestamp,
|
||||
ssbProperties.getAppKey());
|
||||
|
||||
// 指定成功跳转地址
|
||||
String redirectUrl =
|
||||
SecurityUtils.dealRedirectUrl(request, authorizationRequest.getRedirectUri());
|
||||
|
||||
String state = "123";
|
||||
|
||||
// 微信Authorization Request URL
|
||||
String authorizationRequestUri =
|
||||
String.format(AUTH_URL,
|
||||
authorizationRequest.getClientId(),
|
||||
SecurityUtils.encodeURL(redirectUrl),
|
||||
timestamp,
|
||||
sign,
|
||||
state);
|
||||
|
||||
return OAuth2AuthorizationRequest.from(authorizationRequest)
|
||||
.authorizationRequestUri(authorizationRequestUri)
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.newyetai.security.social.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.collections4.map.CaseInsensitiveMap;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.security.social.service.IOauthProvider;
|
||||
|
||||
@Component
|
||||
public class Oauth2ProviderUtils {
|
||||
|
||||
private static Map<String, IOauthProvider> providerMap = new CaseInsensitiveMap<String, IOauthProvider>();
|
||||
|
||||
private Oauth2ProviderUtils(List<IOauthProvider> providers) {
|
||||
providers.forEach(provider -> providerMap.put(provider.getName(), provider));
|
||||
}
|
||||
|
||||
public static IOauthProvider getProvider(String providerName) {
|
||||
IOauthProvider provider = providerMap.get(providerName);
|
||||
return provider;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.newyetai.security.social.wechat.constant;
|
||||
|
||||
/**
|
||||
* @author sr on 2021-01-13
|
||||
*/
|
||||
public interface WechatConstants {
|
||||
|
||||
/** 授权code获取token **/
|
||||
String TOKEN_URL_FORMAT =
|
||||
"https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=%s";
|
||||
|
||||
/** openId换取用户信息 **/
|
||||
String USER_URL_FORMAT =
|
||||
"https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s";
|
||||
|
||||
/** 调起微信授权地址 **/
|
||||
String AUTH_URL_FORMAT =
|
||||
"https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&state=%s#wechat_redirect";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.newyetai.security.social.wechat.convert;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.newyetai.security.social.wechat.constant.WechatConstants;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
/**
|
||||
* code换token
|
||||
*
|
||||
* @author sr on 2021-01-12
|
||||
*/
|
||||
@Slf4j
|
||||
public class WeChatAuthorizationCodeGrantRequestEntityConverter
|
||||
implements Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> {
|
||||
|
||||
@Override
|
||||
public RequestEntity<?> convert(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
HttpHeaders headers = getTokenRequestHeaders();
|
||||
URI uri = buildUri(authorizationCodeGrantRequest);
|
||||
return new RequestEntity<>(headers, HttpMethod.GET, uri);
|
||||
}
|
||||
|
||||
private HttpHeaders getTokenRequestHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
return headers;
|
||||
}
|
||||
|
||||
private URI buildUri(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
|
||||
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
|
||||
String appid = clientRegistration.getClientId();
|
||||
String secret = clientRegistration.getClientSecret();
|
||||
String code = authorizationCodeGrantRequest.getAuthorizationExchange()
|
||||
.getAuthorizationResponse().getCode();
|
||||
String grantType = authorizationCodeGrantRequest.getGrantType().getValue();
|
||||
|
||||
String uriString =
|
||||
String.format(WechatConstants.TOKEN_URL_FORMAT,
|
||||
appid, secret, code, grantType);
|
||||
return UriComponentsBuilder.fromUriString(uriString).build().toUri();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.newyetai.security.social.wechat.convert;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.newyetai.security.social.wechat.constant.WechatConstants;
|
||||
|
||||
public class WeChatUserRequestEntityConverter
|
||||
implements Converter<OAuth2UserRequest, RequestEntity<?>> {
|
||||
|
||||
@Override
|
||||
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
|
||||
HttpHeaders headers = getUserRequestHeaders();
|
||||
URI uri = buildUri(userRequest);
|
||||
return new RequestEntity<>(headers, HttpMethod.GET, uri);
|
||||
}
|
||||
|
||||
private HttpHeaders getUserRequestHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAccept(Collections.singletonList(MediaType.TEXT_PLAIN));
|
||||
return headers;
|
||||
}
|
||||
|
||||
private URI buildUri(OAuth2UserRequest userRequest) {
|
||||
ClientRegistration clientRegistration = userRequest.getClientRegistration();
|
||||
String uri = clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri();
|
||||
String accessToken = userRequest.getAccessToken().getTokenValue();
|
||||
String openId = (String) userRequest.getAdditionalParameters().get("openid");
|
||||
|
||||
String userInfoUrl =
|
||||
String.format(WechatConstants.USER_URL_FORMAT,
|
||||
accessToken, openId, "zh_CN");
|
||||
return UriComponentsBuilder.fromUriString(userInfoUrl).build().toUri();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.newyetai.security.social.wechat.service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.newyetai.common.util.JsonUtils;
|
||||
import com.newyetai.security.social.service.IOauthProvider;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Slf4j
|
||||
public class WeChatAccessTokenResponse {
|
||||
@JsonProperty("access_token")
|
||||
private String accessToken;
|
||||
@JsonProperty("expires_in")
|
||||
private Long expiresIn;
|
||||
@JsonProperty("refresh_token")
|
||||
private String refreshToken;
|
||||
private String openid;
|
||||
private String scope;
|
||||
|
||||
private WeChatAccessTokenResponse() {
|
||||
|
||||
}
|
||||
|
||||
public static WeChatAccessTokenResponse build(String json) {
|
||||
try {
|
||||
return JsonUtils.toObject(json, WeChatAccessTokenResponse.class);
|
||||
} catch (Exception e) {
|
||||
log.error("An error occurred while attempting to parse the WeiXin Access Token Response: "
|
||||
+ e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenResponse toOAuth2AccessTokenResponse(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken);
|
||||
builder.tokenType(OAuth2AccessToken.TokenType.BEARER);
|
||||
builder.expiresIn(expiresIn);
|
||||
builder.refreshToken(refreshToken);
|
||||
|
||||
String[] scopes = scope.split(",");
|
||||
Set<String> scopeSet = new HashSet<>();
|
||||
Collections.addAll(scopeSet, scopes);
|
||||
builder.scopes(scopeSet);
|
||||
|
||||
Map<String, Object> additionalParameters = new LinkedHashMap<>();
|
||||
additionalParameters.put("openid", openid);
|
||||
Map<String, Object> originParam = authorizationCodeGrantRequest.getAuthorizationExchange().getAuthorizationRequest()
|
||||
.getAdditionalParameters();
|
||||
additionalParameters.putAll(originParam);
|
||||
builder.additionalParameters(additionalParameters);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package com.newyetai.security.social.wechat.service;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
|
||||
import com.newyetai.common.util.JsonUtils;
|
||||
import com.newyetai.security.mapstruct.AuthDetailMapMapper;
|
||||
import com.newyetai.security.mapstruct.UserMapMapper;
|
||||
import com.newyetai.security.social.service.IOauthProvider;
|
||||
import com.newyetai.security.social.service.StandardAuthorizationCodeTokenResponseClient;
|
||||
import com.newyetai.security.social.wechat.constant.WechatConstants;
|
||||
import com.newyetai.security.social.wechat.convert.WeChatAuthorizationCodeGrantRequestEntityConverter;
|
||||
import com.newyetai.security.social.wechat.convert.WeChatUserRequestEntityConverter;
|
||||
import com.newyetai.security.util.SecurityUtils;
|
||||
import com.newyetai.usercenter.auth.constant.AuthTypeEnum;
|
||||
import com.newyetai.usercenter.auth.dto.AuthDetailLoginDTO;
|
||||
import com.newyetai.usercenter.auth.wechat.dto.WechatAuthUserInfoDTO;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class WechatOauthLoginProvider implements IOauthProvider {
|
||||
|
||||
private Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> tokenConverter =
|
||||
new WeChatAuthorizationCodeGrantRequestEntityConverter();
|
||||
|
||||
private Converter<OAuth2UserRequest, RequestEntity<?>> userConverter =
|
||||
new WeChatUserRequestEntityConverter();
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public WechatOauthLoginProvider(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return AuthTypeEnum.WECHAT.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest decorateRequest(HttpServletRequest request,
|
||||
OAuth2AuthorizationRequest authorizationRequest) {
|
||||
// 指定成功跳转地址
|
||||
String redirectUrl =
|
||||
SecurityUtils.dealRedirectUrl(request, authorizationRequest.getRedirectUri());
|
||||
|
||||
// 微信Authorization Request URL
|
||||
String authorizationRequestUri =
|
||||
String.format(WechatConstants.AUTH_URL_FORMAT,
|
||||
authorizationRequest.getClientId(),
|
||||
SecurityUtils.encodeURL(redirectUrl),
|
||||
OAuth2AuthorizationResponseType.CODE.getValue(), getScope(authorizationRequest),
|
||||
authorizationRequest.getState());
|
||||
|
||||
return OAuth2AuthorizationRequest.from(authorizationRequest)
|
||||
.authorizationRequestUri(authorizationRequestUri)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String getScope(OAuth2AuthorizationRequest authorizationRequest) {
|
||||
return authorizationRequest.getScopes().stream().findFirst().orElse("snsapi_userinfo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AccessTokenResponse getTokenResponse(
|
||||
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
|
||||
|
||||
// 调用WeChatAuthorizationCodeGrantRequestEntityConverter获取request
|
||||
RequestEntity<?> request = this.tokenConverter.convert(authorizationCodeGrantRequest);
|
||||
|
||||
ResponseEntity<String> response;
|
||||
try {
|
||||
// 执行request
|
||||
response = StandardAuthorizationCodeTokenResponseClient.restOperations.exchange(request,
|
||||
String.class);
|
||||
} catch (RestClientException ex) {
|
||||
String description =
|
||||
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: ";
|
||||
log.error(description, ex);
|
||||
OAuth2Error oauth2Error =
|
||||
new OAuth2Error(
|
||||
StandardAuthorizationCodeTokenResponseClient.INVALID_TOKEN_RESPONSE_ERROR_CODE,
|
||||
description + ex.getMessage(), null);
|
||||
throw new OAuth2AuthorizationException(oauth2Error, ex);
|
||||
}
|
||||
|
||||
// 解析response
|
||||
OAuth2AccessTokenResponse tokenResponse =
|
||||
WeChatAccessTokenResponse.build(response.getBody()).toOAuth2AccessTokenResponse(authorizationCodeGrantRequest);
|
||||
|
||||
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
|
||||
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
|
||||
.scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes())
|
||||
.build();
|
||||
}
|
||||
|
||||
return tokenResponse;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Converter<OAuth2UserRequest, RequestEntity<?>> getUserConvert() {
|
||||
return userConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2User dealUser(OAuth2UserRequest userRequest, String userJsonStr) {
|
||||
WechatAuthUserInfoDTO wechatUserInfoDTO = null;
|
||||
try {
|
||||
// 编码转换
|
||||
userJsonStr = new String(userJsonStr.getBytes("ISO-8859-1"), "UTF-8");
|
||||
wechatUserInfoDTO = JsonUtils.toObject(userJsonStr, WechatAuthUserInfoDTO.class);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.error("An error occurred while attempting to encode userAttributes: " + e.getMessage());
|
||||
}
|
||||
AuthDetailLoginDTO authDetailLoginDTO = AuthDetailMapMapper.INSTANCE.toDto(wechatUserInfoDTO);
|
||||
Integer userType = MapUtils.getInteger(userRequest.getAdditionalParameters(),
|
||||
SecurityConstants.LOGIN_USER_TYPE_KEY);
|
||||
String registerCode = MapUtils.getString(userRequest.getAdditionalParameters(),
|
||||
SecurityConstants.LOGIN_REGISTER_CODE_KEY);
|
||||
String entrypoint = MapUtils.getString(userRequest.getAdditionalParameters(),
|
||||
SecurityConstants.LOGIN_ENTRYPOINT_KEY);
|
||||
authDetailLoginDTO
|
||||
.setUserType(userType);
|
||||
authDetailLoginDTO
|
||||
.setAuthClientId(Long.valueOf(userRequest.getClientRegistration().getRegistrationId()));
|
||||
authDetailLoginDTO.setAuthTypeCode(AuthTypeEnum.WECHAT.getCode());
|
||||
authDetailLoginDTO.setRegisterCode(registerCode);
|
||||
authDetailLoginDTO.setEntrypoint(entrypoint);
|
||||
UserDetailDTO userDetailDTO = userLoginService.findUserDetailDTO(authDetailLoginDTO);
|
||||
return UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.newyetai.security.ssb;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
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 com.newyetai.common.util.JsonUtils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 账号密码认证过滤器,指定请求组装token,调起认证
|
||||
*
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
@Slf4j
|
||||
public class SsbAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
private boolean postOnly = true;
|
||||
|
||||
public static final String REQUEST_METHOD = "GET";
|
||||
public static final String LOGIN_URI = "/h5/ssb_login_notify";
|
||||
public static final AntPathRequestMatcher requiresAuthenticationRequestMatcher =
|
||||
new AntPathRequestMatcher(LOGIN_URI, REQUEST_METHOD);
|
||||
|
||||
public static final String PARAM_PLATFORM = "platform";
|
||||
public static final String PARAM_ACCESS_TOKEN = "access_token";
|
||||
public static final String PARAM_CODE = "code";
|
||||
public static final String PARAM_STATE = "state";
|
||||
|
||||
public SsbAuthenticationFilter() {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
||||
|
||||
if (postOnly && !REQUEST_METHOD.equals(request.getMethod())) {
|
||||
throw new AuthenticationServiceException(
|
||||
"Authentication method not supported: " + request.getMethod());
|
||||
}
|
||||
|
||||
String platform = request.getParameter(PARAM_PLATFORM);
|
||||
String accessToken = request.getParameter(PARAM_ACCESS_TOKEN);
|
||||
String code = request.getParameter(PARAM_CODE);
|
||||
String state = request.getParameter(PARAM_STATE);
|
||||
log.info("ssb login req params:{}", JsonUtils.toJsonString(request.getParameterMap()));
|
||||
AbstractAuthenticationToken authRequest = new SsbAuthenticationToken(
|
||||
SsbLoginDTO.builder().accessToken(accessToken).platform(platform).code(code).state(state).build(), accessToken);
|
||||
|
||||
// Allow subclasses to set the "details" property
|
||||
setDetails(request, authRequest);
|
||||
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
|
||||
protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
|
||||
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.newyetai.security.ssb;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.util.JsonUtils;
|
||||
import com.newyetai.common.validation.util.ValidateUtils;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.security.mapstruct.UserMapMapper;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUsernamePasswordDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 账号密码认证provider,比对信息是否匹配
|
||||
*
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
@Slf4j
|
||||
public class SsbAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public SsbAuthenticationProvider(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
SsbLoginDTO ssbLoginDTO =
|
||||
(SsbLoginDTO) authentication.getPrincipal();
|
||||
log.info("ssb login req:{}", JsonUtils.toJsonString(ssbLoginDTO));
|
||||
String errMsg = ValidateUtils.validate(ssbLoginDTO);
|
||||
Assert.isTrue(errMsg == null, new BadLoginException(errMsg));
|
||||
SecurityUserDTO loadedUser = null;
|
||||
try {
|
||||
UserDetailDTO userDetailDTO = this.userLoginService.loadUser(ssbLoginDTO);
|
||||
loadedUser = UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
|
||||
} catch (BadLoginException be) {
|
||||
throw be;
|
||||
} catch (Exception e) {
|
||||
throw new BadLoginException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
SsbAuthenticationToken result =
|
||||
new SsbAuthenticationToken(loadedUser, listUserGrantedAuthorities(loadedUser));
|
||||
result.setDetails(authentication.getDetails());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return (SsbAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
private Set<GrantedAuthority> listUserGrantedAuthorities(UserDetails userDetails) {
|
||||
// 什么都不用做,访问资源鉴权的时会拿userDetail去缓存获取对应权限
|
||||
return new HashSet<GrantedAuthority>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.ssb;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
|
||||
/**
|
||||
* 账号密码认证信息
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
public class SsbAuthenticationToken extends UsernamePasswordAuthenticationToken {
|
||||
|
||||
public SsbAuthenticationToken(Object principal, Object credentials) {
|
||||
super(principal, credentials);
|
||||
}
|
||||
|
||||
public SsbAuthenticationToken(Object principal, Object credentials,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(principal, credentials, authorities);
|
||||
}
|
||||
|
||||
}
|
||||
34
src/main/java/com/newyetai/security/ssb/SsbLoginDTO.java
Normal file
34
src/main/java/com/newyetai/security/ssb/SsbLoginDTO.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package com.newyetai.security.ssb;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class SsbLoginDTO implements Serializable {
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = 6780283569028235063L;
|
||||
/** 平台标识
|
||||
eshimin: 随申办 APP
|
||||
weixinmini:微信小程序
|
||||
weixinmp:微信公众号
|
||||
alipaymini:支付宝小程序
|
||||
alipayfuwu:支付宝生活号 **/
|
||||
// @NotBlank(message = "平台标识不能为空")
|
||||
private String platform;
|
||||
/** 用户令牌,作为获取用户信息的入参 **/
|
||||
private String accessToken;
|
||||
/** oauth code **/
|
||||
private String code;
|
||||
private String state;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.newyetai.security.temptoken;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
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 com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginPhoneToken;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUserToken;
|
||||
import com.newyetai.usercenter.user.login.dto.TempTokenDTO;
|
||||
|
||||
/**
|
||||
* 临时token认证过滤器,指定请求组装token,调起认证
|
||||
*
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
|
||||
public static final String TYPE_KEY = "type";
|
||||
public static final String REQUEST_METHOD = "POST";
|
||||
|
||||
private static final AntPathRequestMatcher requiresAuthenticationRequestMatcher =
|
||||
new AntPathRequestMatcher("/login_token_" + "{" + TYPE_KEY + "}", REQUEST_METHOD);
|
||||
|
||||
private boolean postOnly = true;
|
||||
|
||||
public TokenAuthenticationFilter() {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
||||
|
||||
if (postOnly && !REQUEST_METHOD.equals(request.getMethod())) {
|
||||
throw new AuthenticationServiceException(
|
||||
"Authentication method not supported: " + request.getMethod());
|
||||
}
|
||||
|
||||
String type = this.resolveType(request);
|
||||
String tempToken = request.getParameter(SecurityConstants.LOGIN_TEMP_TOKEN_KEY);
|
||||
String entrypoint = request.getParameter(SecurityConstants.LOGIN_ENTRYPOINT_KEY);
|
||||
TempTokenDTO tempTokenDTO = null;
|
||||
if (TempTokenDTO.TYPE_PHONE.equals(type)) {
|
||||
Long userId = null;
|
||||
String userIdStr = request.getParameter(SecurityConstants.LOGIN_USER_ID_KEY);
|
||||
if (StringUtils.isNotBlank(userIdStr)) {
|
||||
try {
|
||||
userId = Long.valueOf(userIdStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadLoginException("用户id错误:" + userIdStr);
|
||||
}
|
||||
|
||||
}
|
||||
tempTokenDTO = LoginPhoneToken.builder().tempToken(tempToken).entrypoint(entrypoint)
|
||||
.userId(userId).build();
|
||||
} else if (TempTokenDTO.TYPE_USER.equals(type)) {
|
||||
Long orgId = null;
|
||||
String orgIdStr = request.getParameter(SecurityConstants.LOGIN_ORG_ID_KEY);
|
||||
try {
|
||||
orgId = Long.valueOf(orgIdStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadLoginException("机构id错误:" + orgIdStr);
|
||||
}
|
||||
tempTokenDTO = LoginUserToken.builder().tempToken(tempToken).orgId(orgId).build();
|
||||
} else {
|
||||
throw new BadLoginException("token认证类型有误");
|
||||
}
|
||||
AbstractAuthenticationToken authRequest = new TokenAuthenticationToken(tempTokenDTO, tempToken);
|
||||
|
||||
// Allow subclasses to set the "details" property
|
||||
setDetails(request, authRequest);
|
||||
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
|
||||
protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
|
||||
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
|
||||
private String resolveType(HttpServletRequest request) {
|
||||
if (requiresAuthenticationRequestMatcher.matches(request)) {
|
||||
return requiresAuthenticationRequestMatcher.extractUriTemplateVariables(request)
|
||||
.get(TYPE_KEY);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.newyetai.security.temptoken;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.validation.util.ValidateUtils;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.security.mapstruct.UserMapMapper;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginPhoneToken;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUserToken;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
/**
|
||||
* token认证provider,比对信息是否匹配
|
||||
*
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
public class TokenAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public TokenAuthenticationProvider(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
Object principal = authentication.getPrincipal();
|
||||
|
||||
String errMsg = ValidateUtils.validate(principal);
|
||||
Assert.isTrue(errMsg == null, new BadLoginException(errMsg));
|
||||
SecurityUserDTO loadedUser = null;
|
||||
try {
|
||||
UserDetailDTO userDetailDTO = null;
|
||||
if (principal instanceof LoginPhoneToken) {
|
||||
userDetailDTO = userLoginService.loadUser((LoginPhoneToken) principal);
|
||||
} else if (principal instanceof LoginUserToken) {
|
||||
userDetailDTO = userLoginService.loadUser((LoginUserToken) principal);
|
||||
}else {
|
||||
throw new BadLoginException("token认证类型有误");
|
||||
}
|
||||
loadedUser = UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
|
||||
} catch (BadLoginException be) {
|
||||
throw be;
|
||||
} catch (Exception e) {
|
||||
throw new BadLoginException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
TokenAuthenticationToken result =
|
||||
new TokenAuthenticationToken(loadedUser, listUserGrantedAuthorities(loadedUser));
|
||||
result.setDetails(authentication.getDetails());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return (TokenAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
private Set<GrantedAuthority> listUserGrantedAuthorities(UserDetails userDetails) {
|
||||
// 什么都不用做,访问资源鉴权的时会拿userDetail去缓存获取对应权限
|
||||
return new HashSet<GrantedAuthority>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.newyetai.security.temptoken;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
/**
|
||||
* 临时token认证信息
|
||||
*
|
||||
* @author sr on 2020-06-04
|
||||
*/
|
||||
public class TokenAuthenticationToken extends UsernamePasswordAuthenticationToken {
|
||||
|
||||
public TokenAuthenticationToken(Object principal, Object credentials) {
|
||||
super(principal, credentials);
|
||||
}
|
||||
|
||||
public TokenAuthenticationToken(Object principal, Object credentials,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(principal, credentials, authorities);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.temptoken.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.newyetai.usercenter.user.login.dto.PhoneToken;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder
|
||||
@ApiModel(description = "多用户手机号token")
|
||||
public class DuplMobileToken extends PhoneToken{
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -4119001378908265360L;
|
||||
|
||||
@ApiModelProperty("用户列表")
|
||||
private List<?> users;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.temptoken.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.newyetai.usercenter.user.login.dto.UserToken;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@SuperBuilder
|
||||
@ApiModel(description = "多机构用户token")
|
||||
public class DuplUserToken extends UserToken{
|
||||
|
||||
/** **/
|
||||
private static final long serialVersionUID = -374663184680420441L;
|
||||
|
||||
@ApiModelProperty("机构列表")
|
||||
private List<?> orgs;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.newyetai.security.temptoken.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class PhoneTokenCreateDTO implements Serializable{
|
||||
|
||||
private Integer userType;
|
||||
private String phone;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.newyetai.security.temptoken.dto;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class UserTokenCreateDTO implements Serializable{
|
||||
|
||||
private Integer userType;
|
||||
private Long userId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.newyetai.security.upassword;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
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 com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUsernamePasswordDTO;
|
||||
|
||||
/**
|
||||
* 账号密码认证过滤器,指定请求组装token,调起认证
|
||||
*
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
public class UpasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
|
||||
|
||||
private boolean postOnly = true;
|
||||
|
||||
public static final String REQUEST_METHOD = "POST";
|
||||
public static final AntPathRequestMatcher requiresAuthenticationRequestMatcher =
|
||||
new AntPathRequestMatcher("/login", REQUEST_METHOD);
|
||||
|
||||
public UpasswordAuthenticationFilter() {
|
||||
super(requiresAuthenticationRequestMatcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication attemptAuthentication(HttpServletRequest request,
|
||||
HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
|
||||
|
||||
if (postOnly && !REQUEST_METHOD.equals(request.getMethod())) {
|
||||
throw new AuthenticationServiceException(
|
||||
"Authentication method not supported: " + request.getMethod());
|
||||
}
|
||||
|
||||
String userTypeStr = request.getParameter(SecurityConstants.LOGIN_USER_TYPE_KEY);
|
||||
Integer userType = null;
|
||||
try {
|
||||
userType = Integer.valueOf(userTypeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new BadLoginException("用户类型错误:" + userTypeStr);
|
||||
}
|
||||
|
||||
String username = request.getParameter(SecurityConstants.LOGIN_USERNAME_KEY);
|
||||
String password = request.getParameter(SecurityConstants.LOGIN_PASSWORD_KEY);
|
||||
username = username == null ? "" : username.trim();
|
||||
password = password == null ? "" : password.trim();
|
||||
|
||||
AbstractAuthenticationToken authRequest = new UpasswordAuthenticationToken(
|
||||
LoginUsernamePasswordDTO.builder().username(username).password(password).userType(userType).build(), password);
|
||||
|
||||
// Allow subclasses to set the "details" property
|
||||
setDetails(request, authRequest);
|
||||
|
||||
return this.getAuthenticationManager().authenticate(authRequest);
|
||||
}
|
||||
|
||||
protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
|
||||
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.newyetai.security.upassword;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.validation.util.ValidateUtils;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.expection.BadLoginException;
|
||||
import com.newyetai.security.mapstruct.UserMapMapper;
|
||||
import com.newyetai.usercenter.user.login.dto.LoginUsernamePasswordDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
import com.newyetai.usercenter.user.login.service.UserLoginService;
|
||||
|
||||
/**
|
||||
* 账号密码认证provider,比对信息是否匹配
|
||||
*
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
public class UpasswordAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private UserLoginService userLoginService;
|
||||
|
||||
public UpasswordAuthenticationProvider(UserLoginService userLoginService) {
|
||||
this.userLoginService = userLoginService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
LoginUsernamePasswordDTO loginUsernamePasswordDTO =
|
||||
(LoginUsernamePasswordDTO) authentication.getPrincipal();
|
||||
String errMsg = ValidateUtils.validate(loginUsernamePasswordDTO);
|
||||
Assert.isTrue(errMsg == null, new BadLoginException(errMsg));
|
||||
SecurityUserDTO loadedUser = null;
|
||||
try {
|
||||
UserDetailDTO userDetailDTO = this.userLoginService.loadUser(loginUsernamePasswordDTO);
|
||||
loadedUser = UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
|
||||
} catch (BadLoginException be) {
|
||||
throw be;
|
||||
} catch (Exception e) {
|
||||
throw new BadLoginException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
UpasswordAuthenticationToken result =
|
||||
new UpasswordAuthenticationToken(loadedUser, listUserGrantedAuthorities(loadedUser));
|
||||
result.setDetails(authentication.getDetails());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return (UpasswordAuthenticationToken.class.isAssignableFrom(authentication));
|
||||
}
|
||||
|
||||
private Set<GrantedAuthority> listUserGrantedAuthorities(UserDetails userDetails) {
|
||||
// 什么都不用做,访问资源鉴权的时会拿userDetail去缓存获取对应权限
|
||||
return new HashSet<GrantedAuthority>();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.upassword;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
|
||||
|
||||
/**
|
||||
* 账号密码认证信息
|
||||
* @author sr on 2021-06-21
|
||||
*/
|
||||
public class UpasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
|
||||
|
||||
public UpasswordAuthenticationToken(Object principal, Object credentials) {
|
||||
super(principal, credentials);
|
||||
}
|
||||
|
||||
public UpasswordAuthenticationToken(Object principal, Object credentials,
|
||||
Collection<? extends GrantedAuthority> authorities) {
|
||||
super(principal, credentials, authorities);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.newyetai.security.util;
|
||||
|
||||
/**
|
||||
* request请求host上下文
|
||||
* @author sr on 2021-08-23
|
||||
*/
|
||||
public class HostContextHolder {
|
||||
|
||||
private static final ThreadLocal<String> HOST = new ThreadLocal<String>();
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sr on 2021-08-18
|
||||
*/
|
||||
public static void clearHost() {
|
||||
HOST.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host
|
||||
* @author sr on 2021-08-18
|
||||
*/
|
||||
public static void setHost(String host) {
|
||||
HOST.set(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param host
|
||||
* @author sr on 2021-08-18
|
||||
*/
|
||||
public static String getHost() {
|
||||
return HOST.get();
|
||||
}
|
||||
|
||||
}
|
||||
68
src/main/java/com/newyetai/security/util/MockUtils.java
Normal file
68
src/main/java/com/newyetai/security/util/MockUtils.java
Normal file
@@ -0,0 +1,68 @@
|
||||
package com.newyetai.security.util;
|
||||
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.common.util.UserContextHolder;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.usercenter.constants.OrgConstants;
|
||||
import com.newyetai.usercenter.constants.UserConstants;
|
||||
import com.newyetai.usercenter.user.constant.UserTypeEnum;
|
||||
import com.newyetai.usercenter.user.dto.UserDetailQueryDTO;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-01-05
|
||||
*/
|
||||
public class MockUtils {
|
||||
|
||||
|
||||
/**
|
||||
* 模拟登录机构用户,默认超级管理员/平台机构
|
||||
*/
|
||||
public static void mockPlatUser() {
|
||||
mockUser(UserConstants.SUPERIOR_USER_ID, OrgConstants.PLATFORM_ORG_ID,UserTypeEnum.ORG.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟登录用户
|
||||
*
|
||||
* @param userId
|
||||
* @param orgId(企业用户需要,个人不用可不传)
|
||||
* @author sr on 2020-09-07
|
||||
*/
|
||||
public static void mockUser(Long userId, Long orgId) {
|
||||
mockUser(userId, orgId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟登录用户(能拿到外部线程登录用户信息,则直接用外部的,不要自己定义固定值)
|
||||
* 使用mockUser(userId,orgId), 不需要userType,userId已经有用户类型了
|
||||
* @param userId
|
||||
* @param orgId 个人用户可为空
|
||||
* @param userType {@link UserTypeEnum}
|
||||
* @author sr on 2020-09-07
|
||||
*/
|
||||
@Deprecated
|
||||
public static void mockUser(Long userId, Long orgId,Integer userType) {
|
||||
Assert.notNull(userId, "无userId,无法mock..");
|
||||
UserDetailQueryDTO userDetailQueryDto =
|
||||
UserDetailQueryDTO.builder().userId(userId).orgId(orgId).userType(userType).build();
|
||||
SecurityUserDTO securityUserDTO = UserLoginUtils.loadUserToMock(userDetailQueryDto);
|
||||
SecurityContextHolder.getContext()
|
||||
.setAuthentication(new UsernamePasswordAuthenticationToken(
|
||||
securityUserDTO, null));
|
||||
UserContextHolder.setContext(securityUserDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @author sr on 2020-06-17
|
||||
*/
|
||||
public static void clearUser() {
|
||||
SecurityContextHolder.clearContext();
|
||||
UserContextHolder.clearContext();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
25
src/main/java/com/newyetai/security/util/PasswordUtils.java
Normal file
25
src/main/java/com/newyetai/security/util/PasswordUtils.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.util;
|
||||
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class PasswordUtils {
|
||||
|
||||
private static PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
private PasswordUtils(PasswordEncoder passwordEncoder) {
|
||||
PasswordUtils.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
public static String encode(String password) {
|
||||
return passwordEncoder.encode(password);
|
||||
}
|
||||
|
||||
public static boolean match(String password, String encodedPassword) {
|
||||
return passwordEncoder.matches(password, encodedPassword);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.newyetai.security.util;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.newyetai.common.service.IMockProvider;
|
||||
|
||||
@Service
|
||||
public class SecurityMockProvider implements IMockProvider{
|
||||
|
||||
@Override
|
||||
public void mockPlatUser() {
|
||||
MockUtils.mockPlatUser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mockUser(Long userId, Long orgId) {
|
||||
MockUtils.mockUser(userId, orgId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearUser() {
|
||||
MockUtils.clearUser();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.newyetai.security.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
|
||||
import com.newyetai.common.util.EnvironmentUtils;
|
||||
import com.newyetai.security.config.SecurityProperties;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public abstract class SecurityPropertyUtils {
|
||||
|
||||
private static SecurityProperties securityProperties;
|
||||
|
||||
|
||||
private static List<RequestMatcher> anonRequestMatchers;
|
||||
private static RequestMatcher unacRequestMatcher = new AntPathRequestMatcher("/unac/**");
|
||||
private static RequestMatcher authedRequestMatcher = new AntPathRequestMatcher("/authed/**");
|
||||
|
||||
public static void setSecurityProperties(SecurityProperties securityProperties) {
|
||||
SecurityPropertyUtils.securityProperties = securityProperties;
|
||||
}
|
||||
|
||||
public static SecurityProperties getProperties() {
|
||||
return securityProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据匹配路径获取请求匹配对象
|
||||
*
|
||||
* @param httpMethod
|
||||
* @param antPatterns
|
||||
* @return
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public static List<RequestMatcher> antMatchers(HttpMethod httpMethod,
|
||||
String... antPatterns) {
|
||||
String method = httpMethod == null ? null : httpMethod.toString();
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : antPatterns) {
|
||||
matchers.add(new AntPathRequestMatcher(pattern, method));
|
||||
}
|
||||
return matchers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取匿名配置匹配
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public static List<RequestMatcher> getAnonRequestMatchers() {
|
||||
if (anonRequestMatchers != null) {
|
||||
return anonRequestMatchers;
|
||||
}
|
||||
|
||||
String anonymous = EnvironmentUtils.getProperty(SecurityConstants.PROP_KEY_SECURITY_ANONYMOUS);
|
||||
if (StringUtils.isBlank(anonymous)) {
|
||||
anonRequestMatchers = Collections.emptyList();
|
||||
return anonRequestMatchers;
|
||||
}
|
||||
anonRequestMatchers = antMatchers(null, anonymous.split(","));
|
||||
return anonRequestMatchers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断请求是否匿名请求
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @author sr on 2020-09-10
|
||||
*/
|
||||
public static boolean isAnon(HttpServletRequest request) {
|
||||
List<RequestMatcher> matchers = getAnonRequestMatchers();
|
||||
if (matchers.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (RequestMatcher matcher : matchers) {
|
||||
if (matcher.matches(request)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断请求是否未激活请求
|
||||
*
|
||||
* @param request
|
||||
* @return
|
||||
* @author sr on 2021-06-29
|
||||
*/
|
||||
public static boolean isUnac(HttpServletRequest request) {
|
||||
return unacRequestMatcher.matches(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断请求是否已实名请求
|
||||
* @param request
|
||||
* @return
|
||||
* @author sr on 2021-11-05
|
||||
*/
|
||||
public static boolean isAuthed(HttpServletRequest request) {
|
||||
return authedRequestMatcher.matches(request);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
146
src/main/java/com/newyetai/security/util/SecurityUtils.java
Normal file
146
src/main/java/com/newyetai/security/util/SecurityUtils.java
Normal file
@@ -0,0 +1,146 @@
|
||||
package com.newyetai.security.util;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.newyetai.common.constant.CommonConstants;
|
||||
import com.newyetai.common.util.EnvironmentUtils;
|
||||
import com.newyetai.common.util.RequestUtils;
|
||||
import com.newyetai.usercenter.constants.SecurityConstants;
|
||||
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-06-15
|
||||
*/
|
||||
@Slf4j
|
||||
public class SecurityUtils {
|
||||
|
||||
/** 代理域名 **/
|
||||
private static final String PROXY_DOMAIN = "proxy.niuyetai.cn";
|
||||
|
||||
/** 代理路径请求head-key **/
|
||||
public static final String PROXY_REQUEST_HEADER_KEY = "X-Proxy-Uri";
|
||||
|
||||
|
||||
public static String encodeURL(String url) {
|
||||
try {
|
||||
return URLEncoder.encode(url, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.info("encode失败:{}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String decodeURL(String url) {
|
||||
try {
|
||||
if(url.contains("%")) {
|
||||
return URLDecoder.decode(url, "UTF-8");
|
||||
}
|
||||
return url;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.info("encode失败:{}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否本地security调用 ;security.useLocal=false则关闭(调用户中心),其他都本地调用
|
||||
*
|
||||
* @return
|
||||
* @author sr on 2020-06-15
|
||||
*/
|
||||
public static boolean useLocal() {
|
||||
return !CommonConstants.FALSE_STR
|
||||
.equals(EnvironmentUtils.getProperty(SecurityConstants.PROP_KEY_SECURITY_USELOCAL));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理代理路径
|
||||
* @param request
|
||||
* @param uri
|
||||
* @return
|
||||
* @author sr on 2021-01-16
|
||||
*/
|
||||
private static String dealProxy(HttpServletRequest request, String uri) {
|
||||
String proxyUri = request.getHeader(PROXY_REQUEST_HEADER_KEY);
|
||||
log.info("proxyUri {}", proxyUri);
|
||||
// replace域名到代理域名
|
||||
if (StringUtils.isNotBlank(proxyUri) && uri.contains(PROXY_DOMAIN)) {
|
||||
return uri.replace(PROXY_DOMAIN, PROXY_DOMAIN + proxyUri);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定成功跳转地址
|
||||
*
|
||||
* @param request
|
||||
* @param redirectUri
|
||||
* @return
|
||||
* @author sr on 2021-01-12
|
||||
*/
|
||||
public static String dealRedirectUrl(HttpServletRequest request, final String redirectUri) {
|
||||
String newRedirectUri = redirectUri;
|
||||
String successToUrl = request.getParameter(SecurityConstants.SUCCESS_TO_URL);
|
||||
if (StringUtils.isNotBlank(successToUrl)) {
|
||||
newRedirectUri =
|
||||
String.format(SecurityConstants.REDIRECT_URL_FORMAT, newRedirectUri,
|
||||
encodeURL(successToUrl));
|
||||
}
|
||||
newRedirectUri = dealProxy(request, newRedirectUri);
|
||||
log.info("newRedirectUri:{}", newRedirectUri);
|
||||
return newRedirectUri;
|
||||
}
|
||||
|
||||
public static String dealLoginUrl(HttpServletRequest request, String loginUri) {
|
||||
String successToUrl = request.getParameter(SecurityConstants.SUCCESS_TO_URL);
|
||||
// 指定成功跳转,但登陆地址未配置域名
|
||||
if (StringUtils.isNotBlank(successToUrl)
|
||||
&& loginUri.indexOf(RequestUtils.DOMAIN_SEPARATOR) < 0) {
|
||||
String baseUrl = null;
|
||||
try {
|
||||
baseUrl = URLUtil.getHost(new URL(successToUrl)).toString();
|
||||
} catch (MalformedURLException e) {
|
||||
log.error("successToUrl get domain error:{}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
loginUri = baseUrl + loginUri;
|
||||
}
|
||||
dealProxy(request, loginUri);
|
||||
return loginUri;
|
||||
}
|
||||
|
||||
public static String dealSuccessUrl(HttpServletRequest request, String successUri, String token) {
|
||||
if (successUri == null) {
|
||||
return null;
|
||||
}
|
||||
String redirectSuccessToUrl = request.getParameter(SecurityConstants.SUCCESS_TO_URL);
|
||||
log.debug("redirectSuccessToUrl:{}", redirectSuccessToUrl);
|
||||
successUri =
|
||||
StringUtils.isBlank(redirectSuccessToUrl) ? successUri : decodeURL(redirectSuccessToUrl);
|
||||
if (successUri.indexOf(RequestUtils.DOMAIN_SEPARATOR) > 0) {
|
||||
String requestBaseUrl = RequestUtils.getHttpDomain(request);
|
||||
// 成功地址与当前地址域名不一致,跨域,拼接token
|
||||
if (!successUri.startsWith(requestBaseUrl)) {
|
||||
if (successUri.indexOf("?") > 0) {
|
||||
successUri += "&" + SecurityConstants.TOKEN + "=" + token;
|
||||
} else {
|
||||
successUri += "?" + SecurityConstants.TOKEN + "=" + token;
|
||||
}
|
||||
}
|
||||
}
|
||||
dealProxy(request, successUri);
|
||||
log.info("last redirectSuccessToUrl:{}", successUri);
|
||||
return successUri;
|
||||
}
|
||||
|
||||
}
|
||||
51
src/main/java/com/newyetai/security/util/UserLoginUtils.java
Normal file
51
src/main/java/com/newyetai/security/util/UserLoginUtils.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package com.newyetai.security.util;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.newyetai.common.AlitaResult;
|
||||
import com.newyetai.common.exception.util.Assert;
|
||||
import com.newyetai.security.dto.SecurityUserDTO;
|
||||
import com.newyetai.security.mapstruct.UserMapMapper;
|
||||
import com.newyetai.security.service.IUserSecurityService;
|
||||
import com.newyetai.usercenter.user.dto.UserDetailQueryDTO;
|
||||
import com.newyetai.usercenter.user.login.dto.UserDetailDTO;
|
||||
|
||||
/**
|
||||
* @author sr on 2020-06-15
|
||||
*/
|
||||
@Component
|
||||
public class UserLoginUtils {
|
||||
|
||||
public static final String SPRING_SECURITY_FORM_ORG_ID_KEY = "orgId";
|
||||
|
||||
private static IUserSecurityService userService;
|
||||
|
||||
private UserLoginUtils(IUserSecurityService userService) {
|
||||
UserLoginUtils.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据登录信息获取登录对象
|
||||
*
|
||||
* @param userCloudService
|
||||
* @param userService
|
||||
* @param userDetailQueryDto
|
||||
* @return
|
||||
* @author sr on 2020-06-15
|
||||
*/
|
||||
public static SecurityUserDTO loadUserToMock(UserDetailQueryDTO userDetailQueryDto) {
|
||||
AlitaResult<UserDetailDTO> result = null;
|
||||
result =
|
||||
userService.findUserDetailDTOResult(userDetailQueryDto);
|
||||
UserDetailDTO userDetailDTO = result.getData();
|
||||
Assert.isTrue(result.getSuccess(), "mock失败:" + result.getMsg());
|
||||
return UserMapMapper.INSTANCE.dtoToDto(userDetailDTO);
|
||||
}
|
||||
|
||||
public static AlitaResult<UserDetailDTO> findUserDetailDTOResult(
|
||||
UserDetailQueryDTO userDetailQueryDto) {
|
||||
return userService.findUserDetailDTOResult(userDetailQueryDto);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user