Browse Source

auth

master
朱柏玮 4 years ago
parent
commit
b3a832b5d5
16 changed files with 1171 additions and 0 deletions
  1. +18
    -0
      pigx-auth/Dockerfile
  2. +129
    -0
      pigx-auth/pom.xml
  3. +38
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/PigxAuthApplication.java
  4. +98
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfig.java
  5. +103
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/WebSecurityConfigurer.java
  6. +213
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java
  7. +77
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureEventHandler.java
  8. +69
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessEventHandler.java
  9. +41
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxClientDetailsServiceImpl.java
  10. +123
    -0
      pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxUserDetailsServiceImpl.java
  11. +17
    -0
      pigx-auth/src/main/resources/bootstrap.yml
  12. +87
    -0
      pigx-auth/src/main/resources/logback-spring.xml
  13. +6
    -0
      pigx-auth/src/main/resources/static/css/bootstrap.min.css
  14. +67
    -0
      pigx-auth/src/main/resources/static/css/signin.css
  15. +51
    -0
      pigx-auth/src/main/resources/templates/ftl/confirm.ftl
  16. +34
    -0
      pigx-auth/src/main/resources/templates/ftl/login.ftl

+ 18
- 0
pigx-auth/Dockerfile View File

@@ -0,0 +1,18 @@
FROM pig4cloud/java:8-jre

MAINTAINER wangiegie@gmail.com

ENV TZ=Asia/Shanghai
ENV JAVA_OPTS="-Xms128m -Xmx256m -Djava.security.egd=file:/dev/./urandom"

RUN ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

RUN mkdir -p /pigx-auth

WORKDIR /pigx-auth

EXPOSE 3000

ADD ./target/pigx-auth.jar ./

CMD sleep 120;java $JAVA_OPTS -jar pigx-auth.jar

+ 129
- 0
pigx-auth/pom.xml View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~
~ Copyright (c) 2018-2025, lengleng All rights reserved.
~
~ Redistribution and use in source and binary forms, with or without
~ modification, are permitted provided that the following conditions are met:
~
~ Redistributions of source code must retain the above copyright notice,
~ this list of conditions and the following disclaimer.
~ Redistributions in binary form must reproduce the above copyright
~ notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~ Neither the name of the pig4cloud.com developer nor the names of its
~ contributors may be used to endorse or promote products derived from
~ this software without specific prior written permission.
~ Author: lengleng (wangiegie@gmail.com)
~
-->

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx</artifactId>
<version>3.9.0</version>
</parent>

<artifactId>pigx-auth</artifactId>
<packaging>jar</packaging>

<description>pigx 认证授权中心,基于 spring security oAuth2</description>

<dependencies>
<!--注册中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--配置中心客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--upms api、model 模块-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-upms-api</artifactId>
</dependency>
<!--log-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-log</artifactId>
</dependency>
<!--security-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-security</artifactId>
</dependency>
<!--feign 依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-feign</artifactId>
</dependency>
<!--mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--缓存操作-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-data</artifactId>
</dependency>
<!--sentinel 依赖-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-sentinel</artifactId>
</dependency>
<!--路由控制-->
<dependency>
<groupId>com.pig4cloud</groupId>
<artifactId>pigx-common-gray</artifactId>
</dependency>
<!--JDBC相关-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<!--freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--web 模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--undertow容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<configuration>
<skip>false</skip>
</configuration>
</plugin>
</plugins>
</build>

</project>

+ 38
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/PigxAuthApplication.java View File

@@ -0,0 +1,38 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/

package com.pig4cloud.pigx.auth;

import com.pig4cloud.pigx.common.feign.annotation.EnablePigxFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;

/**
* @author lengleng
* @date 2020-02-08 认证授权中心
*/
@SpringCloudApplication
@EnablePigxFeignClients
public class PigxAuthApplication {

public static void main(String[] args) {
SpringApplication.run(PigxAuthApplication.class, args);
}

}

+ 98
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/AuthorizationServerConfig.java View File

@@ -0,0 +1,98 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/

package com.pig4cloud.pigx.auth.config;

import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.common.security.component.PigxWebResponseExceptionTranslator;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
* @author lengleng
* @date 2018/6/22 认证服务器配置
*/
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

private final ClientDetailsService pigxClientDetailsServiceImpl;

private final AuthenticationManager authenticationManagerBean;

private final RedisConnectionFactory redisConnectionFactory;

private final UserDetailsService pigxUserDetailsService;

private final TokenEnhancer pigxTokenEnhancer;

@Override
@SneakyThrows
public void configure(ClientDetailsServiceConfigurer clients) {
clients.withClientDetails(pigxClientDetailsServiceImpl);
}

@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
oauthServer.allowFormAuthenticationForClients().checkTokenAccess("isAuthenticated()");
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST).tokenStore(tokenStore())
.tokenEnhancer(pigxTokenEnhancer).userDetailsService(pigxUserDetailsService)
.authenticationManager(authenticationManagerBean).reuseRefreshTokens(false)
.pathMapping("/oauth/confirm_access", "/token/confirm_access")
.exceptionTranslator(new PigxWebResponseExceptionTranslator());
}

@Bean
public TokenStore tokenStore() {
RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
tokenStore.setPrefix(SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX);
tokenStore.setAuthenticationKeyGenerator(new DefaultAuthenticationKeyGenerator() {
@Override
public String extractKey(OAuth2Authentication authentication) {
return super.extractKey(authentication) + StrUtil.COLON + TenantContextHolder.getTenantId();
}
});
return tokenStore;
}

}

+ 103
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/config/WebSecurityConfigurer.java View File

@@ -0,0 +1,103 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/

package com.pig4cloud.pigx.auth.config;

import com.pig4cloud.pigx.common.security.handler.FormAuthenticationFailureHandler;
import com.pig4cloud.pigx.common.security.handler.MobileLoginSuccessHandler;
import com.pig4cloud.pigx.common.security.mobile.MobileSecurityConfigurer;
import lombok.SneakyThrows;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

/**
* @author lengleng
* @date 2018/6/22 认证相关配置
*/
@Primary
@Order(90)
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

@Override
@SneakyThrows
protected void configure(HttpSecurity http) {
http.formLogin().loginPage("/token/login").loginProcessingUrl("/token/form")
.failureHandler(authenticationFailureHandler()).and().logout()
.logoutSuccessHandler((request, response, authentication) -> {
String referer = request.getHeader(HttpHeaders.REFERER);
response.sendRedirect(referer);
}).deleteCookies("JSESSIONID").invalidateHttpSession(true).and().authorizeRequests()
.antMatchers("/token/**", "/actuator/**", "/mobile/**").permitAll().anyRequest().authenticated().and()
.csrf().disable().apply(mobileSecurityConfigurer());
}

/**
* 不拦截静态资源
* @param web
*/
@Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/css/**");
}

@Bean
@Override
@SneakyThrows
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}

@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new FormAuthenticationFailureHandler();
}

@Bean
public AuthenticationSuccessHandler mobileLoginSuccessHandler() {
return new MobileLoginSuccessHandler();
}

@Bean
public MobileSecurityConfigurer mobileSecurityConfigurer() {
return new MobileSecurityConfigurer();
}

/**
* https://spring.io/blog/2017/11/01/spring-security-5-0-0-rc1-released#password-storage-updated
* Encoded password does not look like BCrypt
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

}

+ 213
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/endpoint/PigxTokenEndpoint.java View File

@@ -0,0 +1,213 @@
/*
*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*
*/

package com.pig4cloud.pigx.auth.endpoint;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.PaginationConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import com.pig4cloud.pigx.common.security.annotation.Inner;
import com.pig4cloud.pigx.common.security.util.SecurityUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.ConvertingCursor;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.HttpHeaders;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
* @author lengleng
* @date 2018/6/24 删除token端点
*/
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/token")
public class PigxTokenEndpoint {

private static final String PIGX_OAUTH_ACCESS = SecurityConstants.PIGX_PREFIX + SecurityConstants.OAUTH_PREFIX
+ "auth_to_access:";

private final ClientDetailsService clientDetailsService;

private final RedisTemplate redisTemplate;

private final TokenStore tokenStore;

private final CacheManager cacheManager;

/**
* 认证页面
* @param modelAndView
* @param error 表单登录失败处理回调的错误信息
* @return ModelAndView
*/
@GetMapping("/login")
public ModelAndView require(ModelAndView modelAndView, @RequestParam(required = false) String error) {
modelAndView.setViewName("ftl/login");
modelAndView.addObject("error", error);
return modelAndView;
}

/**
* 确认授权页面
* @param request
* @param session
* @param modelAndView
* @return
*/
@GetMapping("/confirm_access")
public ModelAndView confirm(HttpServletRequest request, HttpSession session, ModelAndView modelAndView) {
Map<String, Object> scopeList = (Map<String, Object>) request.getAttribute("scopes");
modelAndView.addObject("scopeList", scopeList.keySet());

Object auth = session.getAttribute("authorizationRequest");
if (auth != null) {
AuthorizationRequest authorizationRequest = (AuthorizationRequest) auth;
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(authorizationRequest.getClientId());
modelAndView.addObject("app", clientDetails.getAdditionalInformation());
modelAndView.addObject("user", SecurityUtils.getUser());
}

modelAndView.setViewName("ftl/confirm");
return modelAndView;
}

/**
* 退出token
* @param authHeader Authorization
*/
@DeleteMapping("/logout")
public R logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
if (StrUtil.isBlank(authHeader)) {
return R.ok(Boolean.FALSE, "退出失败,token 为空");
}

String tokenValue = authHeader.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim();
return delToken(tokenValue);
}

/**
* 令牌管理调用
* @param token token
* @return
*/
@Inner
@DeleteMapping("/{token}")
public R<Boolean> delToken(@PathVariable("token") String token) {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(token);
if (accessToken == null || StrUtil.isBlank(accessToken.getValue())) {
return R.ok(Boolean.TRUE, "退出失败,token 无效");
}

OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(accessToken);
// 清空用户信息
cacheManager.getCache(CacheConstants.USER_DETAILS).evict(auth2Authentication.getName());

// 清空access token
tokenStore.removeAccessToken(accessToken);

// 清空 refresh token
OAuth2RefreshToken refreshToken = accessToken.getRefreshToken();
tokenStore.removeRefreshToken(refreshToken);
return R.ok();
}

/**
* 查询token
* @param params 分页参数
* @return
*/
@Inner
@PostMapping("/page")
public R<Page> tokenList(@RequestBody Map<String, Object> params) {
// 根据分页参数获取对应数据
String key = String.format("%s*:%s", PIGX_OAUTH_ACCESS, TenantContextHolder.getTenantId());
List<String> pages = findKeysForPage(key, MapUtil.getInt(params, PaginationConstants.CURRENT),
MapUtil.getInt(params, PaginationConstants.SIZE));

redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
Page result = new Page(MapUtil.getInt(params, PaginationConstants.CURRENT),
MapUtil.getInt(params, PaginationConstants.SIZE));
result.setRecords(redisTemplate.opsForValue().multiGet(pages));
result.setTotal(redisTemplate.keys(key).size());
return R.ok(result);
}

private List<String> findKeysForPage(String patternKey, int pageNum, int pageSize) {
ScanOptions options = ScanOptions.scanOptions().count(1000L).match(patternKey).build();
RedisSerializer<String> redisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
Cursor cursor = (Cursor) redisTemplate.executeWithStickyConnection(
redisConnection -> new ConvertingCursor<>(redisConnection.scan(options), redisSerializer::deserialize));
List<String> result = new ArrayList<>();
int tmpIndex = 0;
int startIndex = (pageNum - 1) * pageSize;
int end = pageNum * pageSize;

assert cursor != null;
while (cursor.hasNext()) {
if (tmpIndex >= startIndex && tmpIndex < end) {
result.add(cursor.next().toString());
tmpIndex++;
continue;
}
if (tmpIndex >= end) {
break;
}
tmpIndex++;
cursor.next();
}

try {
cursor.close();
}
catch (IOException e) {
log.error("关闭cursor 失败");
}
return result;
}

}

+ 77
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationFailureEventHandler.java View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/

package com.pig4cloud.pigx.auth.handler;

import com.pig4cloud.pigx.admin.api.entity.SysLog;
import com.pig4cloud.pigx.admin.api.feign.RemoteLogService;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import com.pig4cloud.pigx.common.log.util.SysLogUtils;
import com.pig4cloud.pigx.common.security.handler.AuthenticationFailureHandler;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @author lengleng
* @date 2018/10/8
*/
@Slf4j
@Component
@AllArgsConstructor
public class PigxAuthenticationFailureEventHandler implements AuthenticationFailureHandler {

private final RemoteLogService logService;

/**
* 异步处理,登录失败方法
* <p>
* @param authenticationException 登录的authentication 对象
* @param authentication 登录的authenticationException 对象
* @param request 请求
* @param response 响应
*/
@Async
@Override
@SneakyThrows
public void handle(AuthenticationException authenticationException, Authentication authentication,
HttpServletRequest request, HttpServletResponse response) {
String username = authentication.getName();
SysLog sysLog = SysLogUtils.getSysLog(request, username);
sysLog.setTitle(username + "用户登录");
sysLog.setType(CommonConstants.STATUS_LOCK);
sysLog.setParams(username);
sysLog.setException(authenticationException.getLocalizedMessage());
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
sysLog.setServiceId(WebUtils.extractClientId(header).orElse("N/A"));

logService.saveLog(sysLog, SecurityConstants.FROM_IN);

log.info("用户:{} 登录失败,异常:{}", username, authenticationException.getLocalizedMessage());
}

}

+ 69
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/handler/PigxAuthenticationSuccessEventHandler.java View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/

package com.pig4cloud.pigx.auth.handler;

import com.pig4cloud.pigx.admin.api.entity.SysLog;
import com.pig4cloud.pigx.admin.api.feign.RemoteLogService;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.WebUtils;
import com.pig4cloud.pigx.common.log.util.SysLogUtils;
import com.pig4cloud.pigx.common.security.handler.AuthenticationSuccessHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @author lengleng
* @date 2018/10/8
*/
@Slf4j
@Component
@AllArgsConstructor
public class PigxAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler {

private final RemoteLogService logService;

/**
* 处理登录成功方法
* <p>
* 获取到登录的authentication 对象
* @param authentication 登录对象
* @param request 请求
* @param response 返回
*/
@Async
@Override
public void handle(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
String username = authentication.getName();
SysLog sysLog = SysLogUtils.getSysLog(request, username);
sysLog.setTitle(username + "用户登录");
sysLog.setParams(username);
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
sysLog.setServiceId(WebUtils.extractClientId(header).orElse("N/A"));

logService.saveLog(sysLog, SecurityConstants.FROM_IN);
log.info("用户:{} 登录成功", username);
}

}

+ 41
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxClientDetailsServiceImpl.java View File

@@ -0,0 +1,41 @@
package com.pig4cloud.pigx.auth.service;

import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.data.tenant.TenantContextHolder;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;

/**
* @author lengleng
* @date 2020/03/25
* <p>
* 扩展 JdbcClientDetailsService 支持多租户
*/
@Service
public class PigxClientDetailsServiceImpl extends JdbcClientDetailsService {

public PigxClientDetailsServiceImpl(DataSource dataSource) {
super(dataSource);
}

/**
* 重写原生方法支持redis缓存
* @param clientId
* @return ClientDetails
* @throws InvalidClientException
*/
@Override
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
public ClientDetails loadClientByClientId(String clientId) {
super.setSelectClientDetailsSql(
String.format(SecurityConstants.DEFAULT_SELECT_STATEMENT, TenantContextHolder.getTenantId()));
return super.loadClientByClientId(clientId);
}

}

+ 123
- 0
pigx-auth/src/main/java/com/pig4cloud/pigx/auth/service/PigxUserDetailsServiceImpl.java View File

@@ -0,0 +1,123 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/

package com.pig4cloud.pigx.auth.service;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.pig4cloud.pigx.admin.api.dto.UserInfo;
import com.pig4cloud.pigx.admin.api.entity.SysUser;
import com.pig4cloud.pigx.admin.api.feign.RemoteUserService;
import com.pig4cloud.pigx.common.core.constant.CacheConstants;
import com.pig4cloud.pigx.common.core.constant.CommonConstants;
import com.pig4cloud.pigx.common.core.constant.SecurityConstants;
import com.pig4cloud.pigx.common.core.util.R;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.common.security.service.PigxUserDetailsService;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
* 用户详细信息
*
* @author lengleng
*/
@Slf4j
@Service
@AllArgsConstructor
public class PigxUserDetailsServiceImpl implements PigxUserDetailsService {

private final RemoteUserService remoteUserService;

private final CacheManager cacheManager;

/**
* 用户密码登录
* @param username 用户名
* @return
* @throws UsernameNotFoundException
*/
@Override
@SneakyThrows
public UserDetails loadUserByUsername(String username) {
Cache cache = cacheManager.getCache(CacheConstants.USER_DETAILS);
if (cache != null && cache.get(username) != null) {
return (PigxUser) cache.get(username).get();
}

R<UserInfo> result = remoteUserService.info(username, SecurityConstants.FROM_IN);
UserDetails userDetails = getUserDetails(result);
cache.put(username, userDetails);
return userDetails;
}

/**
* 根据社交登录code 登录
* @param inStr TYPE@CODE
* @return UserDetails
* @throws UsernameNotFoundException
*/
@Override
@SneakyThrows
public UserDetails loadUserBySocial(String inStr) {
return getUserDetails(remoteUserService.social(inStr, SecurityConstants.FROM_IN));
}

/**
* 构建userdetails
* @param result 用户信息
* @return
*/
private UserDetails getUserDetails(R<UserInfo> result) {
if (result == null || result.getData() == null) {
throw new UsernameNotFoundException("用户不存在");
}

UserInfo info = result.getData();
Set<String> dbAuthsSet = new HashSet<>();
if (ArrayUtil.isNotEmpty(info.getRoles())) {
// 获取角色
Arrays.stream(info.getRoles()).forEach(roleId -> dbAuthsSet.add(SecurityConstants.ROLE + roleId));
// 获取资源
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));

}
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
boolean enabled = StrUtil.equals(user.getLockFlag(), CommonConstants.STATUS_NORMAL);
// 构造security用户

return new PigxUser(user.getUserId(), user.getDeptId(), user.getPhone(), user.getAvatar(), user.getTenantId(),
user.getUsername(), SecurityConstants.BCRYPT + user.getPassword(), enabled, true, true,
!CommonConstants.STATUS_LOCK.equals(user.getLockFlag()), authorities);
}

}

+ 17
- 0
pigx-auth/src/main/resources/bootstrap.yml View File

@@ -0,0 +1,17 @@
server:
port: 3000

spring:
application:
name: @artifactId@
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:pigx-register}:${NACOS_PORT:8848}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
file-extension: yml
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
profiles:
active: @profiles.active@

+ 87
- 0
pigx-auth/src/main/resources/logback-spring.xml View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2018-2025, lengleng All rights reserved.
~
~ Redistribution and use in source and binary forms, with or without
~ modification, are permitted provided that the following conditions are met:
~
~ Redistributions of source code must retain the above copyright notice,
~ this list of conditions and the following disclaimer.
~ Redistributions in binary form must reproduce the above copyright
~ notice, this list of conditions and the following disclaimer in the
~ documentation and/or other materials provided with the distribution.
~ Neither the name of the pig4cloud.com developer nor the names of its
~ contributors may be used to endorse or promote products derived from
~ this software without specific prior written permission.
~ Author: lengleng (wangiegie@gmail.com)
-->

<!--
小技巧: 在根pom里面设置统一存放路径,统一管理方便维护
<properties>
<log-path>/Users/lengleng</log-path>
</properties>
1. 其他模块加日志输出,直接copy本文件放在resources 目录即可
2. 注意修改 <property name="${log-path}/log.path" value=""/> 的value模块
-->
<configuration debug="false" scan="false">
<property name="log.path" value="logs/${project.artifactId}"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- Console log output -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>

<!-- Log file debug output -->
<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
</appender>

<!-- Log file error output -->
<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%date [%thread] %-5level [%logger{50}] %file:%line - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>

<logger name="org.activiti.engine.impl.db" level="DEBUG">
<appender-ref ref="debug"/>
</logger>

<!--nacos 心跳 INFO 屏蔽-->
<logger name="com.alibaba.nacos" level="OFF">
<appender-ref ref="error"/>
</logger>
<!-- Level: FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7 -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="debug"/>
</root>
</configuration>

+ 6
- 0
pigx-auth/src/main/resources/static/css/bootstrap.min.css
File diff suppressed because it is too large
View File


+ 67
- 0
pigx-auth/src/main/resources/static/css/signin.css View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2018-2025, lengleng All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of the pig4cloud.com developer nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
* Author: lengleng (wangiegie@gmail.com)
*/

.sign_body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}

.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-margin-top {
margin-top: 50px;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
height: auto;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
font-size: 16px;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
footer{
text-align: center;
position:absolute;
bottom:0;
width:100%;
height:100px;
}

+ 51
- 0
pigx-auth/src/main/resources/templates/ftl/confirm.ftl View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
<title>PigX第三方授权</title>
<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
<link rel="stylesheet" type="text/css" href="/css/signin.css"/>
</head>

<body>
<nav class="navbar navbar-default container-fluid">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">开放平台</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-5">
<p class="navbar-text navbar-right">
<a target="_blank" href="https://pig4cloud.com">技术支持</a>
</p>
<p class="navbar-text navbar-right">
<a target="_blank" href="https://pig4cloud.com">${user.username}</a>
</p>
</div>
</div>
</nav>
<div style="padding-top: 80px;width: 300px; color: #555; margin:0px auto;">
<form id='confirmationForm' name='confirmationForm' action="/oauth/authorize" method='post'>
<input name='user_oauth_approval' value='true' type='hidden'/>
<p>
<a href="${app.website!''}" target="_blank">${app.appName!'未定义应用名称'}</a> 将获得以下权限:</p>
<ul class="list-group">
<li class="list-group-item"> <span>
<#list scopeList as scope>
<input type="hidden" name="${scope}" value="true"/>
<input type="checkbox" disabled checked="checked"/><label>${scope}</label>
</#list>
</ul>
<p class="help-block">授权后表明你已同意 <a>服务协议</a></p>
<button class="btn btn-success pull-right" type="submit" id="write-email-btn">授权</button>
</p>
</form>
</div>
<footer>
<p>support by: pig4cloud.com</p>
<p>email: <a href="mailto:wangiegie@gmail.com">wangiegie@gmail.com</a>.</p>
</footer>
</body>
</html>

+ 34
- 0
pigx-auth/src/main/resources/templates/ftl/login.ftl View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="">

<title>PigX微服务统一认证</title>

<link href="/css/bootstrap.min.css" rel="stylesheet">
<link href="/css/signin.css" rel="stylesheet">
</head>

<body class="sign_body">
<div class="container form-margin-top">
<form class="form-signin" action="/token/form" method="post">
<h2 class="form-signin-heading" align="center">统一认证系统</h2>
<input type="text" name="username" class="form-control form-margin-top" placeholder="账号" required autofocus>
<input type="password" name="password" class="form-control" placeholder="密码" required>
<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button>
<#if error??>
<span style="color: red; ">${error}</span>
</#if>
</form>
</div>
<footer>
<p>support by: pig4cloud</p>
<p>email: <a href="mailto:pig4cloud@qq.com">pig4cloud@qq.com</a>.</p>
</footer>
</body>
</html>

Loading…
Cancel
Save