Skip to content

Commit

Permalink
feat(auth): 单体模式下支持 授权码模式
Browse files Browse the repository at this point in the history
http://localhost:9999/admin/oauth2/authorize?scope=server&client_id=test&response_type=code&redirect_uri=https://pig4cloud.com
  • Loading branch information
lltx committed Dec 15, 2024
1 parent 2ff0c87 commit 90fac31
Show file tree
Hide file tree
Showing 15 changed files with 220 additions and 303 deletions.
2 changes: 1 addition & 1 deletion db/pig_config.sql
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ CREATE TABLE `config_info` (
-- ----------------------------
BEGIN;
INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (1, 'application-dev.yml', 'DEFAULT_GROUP', '# 配置文件加密根密码\njasypt:\n encryptor:\n password: pig\n algorithm: PBEWithMD5AndDES\n iv-generator-classname: org.jasypt.iv.NoIvGenerator\n \n# Spring 相关\nspring:\n cache:\n type: redis\n data:\n redis:\n host: pig-redis\n cloud:\n sentinel:\n eager: true\n transport:\n dashboard: pig-sentinel:5003\n openfeign:\n sentinel:\n enabled: true\n okhttp:\n enabled: true\n httpclient:\n enabled: false\n compression:\n request:\n enabled: true\n response:\n enabled: true\n\n# 暴露监控端点\nmanagement:\n endpoints:\n web:\n exposure:\n include: \"*\" \n endpoint:\n health:\n show-details: ALWAYS\n\n# mybaits-plus配置\nmybatis-plus:\n mapper-locations: classpath:/mapper/*Mapper.xml\n global-config:\n banner: false\n db-config:\n id-type: auto\n table-underline: true\n logic-delete-value: 1\n logic-not-delete-value: 0\n type-handlers-package: com.pig4cloud.pig.common.mybatis.handler\n configuration:\n map-underscore-to-camel-case: true\n shrink-whitespaces-in-sql: true\n\n# swagger 配置\nswagger:\n enabled: true\n title: Pig Swagger API\n gateway: http://${GATEWAY_HOST:pig-gateway}:${GATEWAY-PORT:9999}\n token-url: ${swagger.gateway}/auth/oauth2/token\n scope: server\n services:\n pig-upms-biz: admin\n pig-codegen: gen', 'e8b519db39e79d600a957cf47b3306d5', '2022-05-08 12:10:37', '2024-03-28 14:05:12', 'nacos', '0:0:0:0:0:0:0:1', '', '', '', '', '', 'yaml', '', '');
INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\n freemarker:\n allow-request-override: false\n allow-session-override: false\n cache: true\n charset: UTF-8\n check-template-location: true\n content-type: text/html\n enabled: true\n expose-request-attributes: false\n expose-session-attributes: false\n expose-spring-macro-helpers: true\n prefer-file-system-access: true\n suffix: .ftl\n template-loader-path: classpath:/templates/\n\n\nsecurity:\n encode-key: \'thanks,pig4cloud\'\n ignore-clients: \n - test', '1d14791d3c4c153acd3bee55fc114bd6', '2022-05-08 12:10:37', '2024-04-04 21:40:47', 'nacos', '0:0:0:0:0:0:0:1', '', '', '', '', '', 'yaml', '', '');
INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (2, 'pig-auth-dev.yml', 'DEFAULT_GROUP', '# 数据源\nspring:\n freemarker:\n allow-request-override: false\n allow-session-override: false\n cache: true\n charset: UTF-8\n check-template-location: true\n content-type: text/html\n enabled: true\n request-context-attribute: request\n expose-request-attributes: false\n expose-session-attributes: false\n expose-spring-macro-helpers: true\n prefer-file-system-access: true\n suffix: .ftl\n template-loader-path: classpath:/templates/\n\n\nsecurity:\n encode-key: \'thanks,pig4cloud\'\n ignore-clients:\n - test\n - client\n - open\n - app', 'b4a660ece61e8180b4940a0770eddfee', '2022-05-08 12:10:37', '2024-12-14 23:56:48', 'nacos', '127.0.0.1', '', '', '', '', '', 'yaml', '', '');
INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (3, 'pig-codegen-dev.yml', 'DEFAULT_GROUP', '# 数据源配置\nspring:\n datasource:\n type: com.zaxxer.hikari.HikariDataSource\n driver-class-name: com.mysql.cj.jdbc.Driver\n username: root\n password: root\n url: jdbc:mysql://pig-mysql:3306/pig?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true\n resources:\n static-locations: classpath:/static/,classpath:/views/\n', 'cf786dbe3b07074fc187bf2eab3266b1', '2022-05-08 12:10:37', '2023-01-28 14:05:36', '', '0:0:0:0:0:0:0:1', '', '', '', '', '', 'yaml', '', '');
INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (4, 'pig-gateway-dev.yml', 'DEFAULT_GROUP', 'spring:\n cloud:\n gateway:\n locator:\n enabled: true\n routes:\n # 认证中心\n - id: pig-auth\n uri: lb://pig-auth\n predicates:\n - Path=/auth/**\n #UPMS 模块\n - id: pig-upms-biz\n uri: lb://pig-upms-biz\n predicates:\n - Path=/admin/**\n filters:\n # 限流配置\n - name: RequestRateLimiter\n args:\n key-resolver: \'#{@remoteAddrKeyResolver}\'\n redis-rate-limiter.replenishRate: 100\n redis-rate-limiter.burstCapacity: 200\n # 代码生成模块\n - id: pig-codegen\n uri: lb://pig-codegen\n predicates:\n - Path=/gen/**\n # 代码生成模块\n - id: pig-quartz\n uri: lb://pig-quartz\n predicates:\n - Path=/job/**\n # 固定路由转发配置 无修改\n - id: openapi\n uri: lb://pig-gateway\n predicates:\n - Path=/v3/api-docs/**\n filters:\n - RewritePath=/v3/api-docs/(?<path>.*), /$\\{path}/$\\{path}/v3/api-docs', '7940b3e89a9489e0844af6b1dc91d65b', '2022-05-08 12:10:37', '2024-04-04 21:40:32', 'nacos', '0:0:0:0:0:0:0:1', '', '', '', '', '', 'yaml', '', '');
INSERT INTO `config_info` (`id`, `data_id`, `group_id`, `content`, `md5`, `gmt_create`, `gmt_modified`, `src_user`, `src_ip`, `app_name`, `tenant_id`, `c_desc`, `c_use`, `effect`, `type`, `c_schema`, `encrypted_data_key`) VALUES (5, 'pig-monitor-dev.yml', 'DEFAULT_GROUP', 'spring:\n autoconfigure:\n exclude: com.pig4cloud.pig.common.core.config.JacksonConfiguration\n # 安全配置\n security:\n user:\n name: ENC(8Hk2ILNJM8UTOuW/Xi75qg==) # pig\n password: ENC(o6cuPFfUevmTbkmBnE67Ow====) # pig\n', '650bdfa15f60f3faa84dfe6e6878b8cf', '2022-05-08 12:10:37', '2022-05-08 12:10:37', NULL, '127.0.0.1', '', '', NULL, NULL, NULL, 'yaml', NULL, '');
Expand Down
2 changes: 1 addition & 1 deletion pig-auth/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--freemarker-->
<!--freemarker 授权码模式渲染-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import com.pig4cloud.pig.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider;
import com.pig4cloud.pig.common.core.constant.SecurityConstants;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
Expand All @@ -44,12 +43,15 @@
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import java.util.Arrays;

Expand All @@ -69,10 +71,17 @@ public class AuthorizationServerConfiguration {

private final ValidateCodeFilter validateCodeFilter;

/**
* Authorization Server 配置,仅对 /oauth2/** 的请求有效
* @param http http
* @return {@link SecurityFilterChain }
* @throws Exception 异常
*/
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnProperty(value = "security.micro", matchIfMissing = true)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
public SecurityFilterChain authorizationServer(HttpSecurity http) throws Exception {
// 配置授权服务器的安全策略,只有/oauth2/**的请求才会走如下的配置
http.securityMatcher("/oauth2/**");
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();

// 增加验证码过滤器
Expand All @@ -87,22 +96,16 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
}).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证
oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new PigAuthenticationFailureEventHandler()))// 处理客户端认证异常
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面
.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults());

AntPathRequestMatcher[] requestMatchers = new AntPathRequestMatcher[] {
AntPathRequestMatcher.antMatcher("/token/**"), AntPathRequestMatcher.antMatcher("/actuator/**"),
AntPathRequestMatcher.antMatcher("/code/image"), AntPathRequestMatcher.antMatcher("/css/**"),
AntPathRequestMatcher.antMatcher("/error") };

http.authorizeHttpRequests(authorizeRequests -> {
// 自定义接口、端点暴露
authorizeRequests.requestMatchers(requestMatchers).permitAll();
authorizeRequests.anyRequest().authenticated();
})
.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
.authorizationServerSettings(
AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()),
Customizer.withDefaults());
.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)), Customizer.withDefaults())
.authorizeHttpRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated());

// 设置 Token 存储的策略
http.with(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
.authorizationServerSettings(
AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()),
Customizer.withDefaults());

// 设置授权码模式登录页面
http.with(new FormIdentityLoginConfigurer(), Customizer.withDefaults());
DefaultSecurityFilterChain securityFilterChain = http.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@
*/
@Slf4j
@RestController
@RequestMapping
@RequiredArgsConstructor
@RequestMapping("/token")
public class PigTokenEndpoint {

private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
Expand All @@ -90,19 +90,23 @@ public class PigTokenEndpoint {
private final CacheManager cacheManager;

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

@GetMapping("/confirm_access")
/**
* 授权码模式:确认页面
* @return {@link ModelAndView }
*/
@GetMapping("/oauth2/confirm_access")
public ModelAndView confirm(Principal principal, ModelAndView modelAndView,
@RequestParam(OAuth2ParameterNames.CLIENT_ID) String clientId,
@RequestParam(OAuth2ParameterNames.SCOPE) String scope,
Expand All @@ -121,10 +125,11 @@ public ModelAndView confirm(Principal principal, ModelAndView modelAndView,
}

/**
* 退出并删除token
* @param authHeader Authorization
* 注销并删除令牌
* @param authHeader auth 标头
* @return {@link R }<{@link Boolean }>
*/
@DeleteMapping("/logout")
@DeleteMapping("/token/logout")
public R<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authHeader) {
if (StrUtil.isBlank(authHeader)) {
return R.ok();
Expand All @@ -135,11 +140,13 @@ public R<Boolean> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION, requi
}

/**
* 校验token
* @param token 令牌
* 检查令牌
* @param token 令 牌
* @param response 响应
* @param request 请求
*/
@SneakyThrows
@GetMapping("/check_token")
@GetMapping("/token/check_token")
public void checkToken(String token, HttpServletResponse response, HttpServletRequest request) {
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);

Expand All @@ -165,11 +172,12 @@ public void checkToken(String token, HttpServletResponse response, HttpServletRe
}

/**
* 令牌管理调用
* @param token token
* 删除令牌
* @param token 令 牌
* @return {@link R }<{@link Boolean }>
*/
@Inner
@DeleteMapping("/remove/{token}")
@DeleteMapping("/token/remove/{token}")
public R<Boolean> removeToken(@PathVariable("token") String token) {
OAuth2Authorization authorization = authorizationService.findByToken(token, OAuth2TokenType.ACCESS_TOKEN);
if (authorization == null) {
Expand All @@ -191,12 +199,12 @@ public R<Boolean> removeToken(@PathVariable("token") String token) {
}

/**
* 查询token
* @param params 分页参数
* @return
* 令牌列表
* @param params 参数
* @return {@link R }<{@link Page }>
*/
@Inner
@PostMapping("/page")
@PostMapping("/token/page")
public R<Page> tokenList(@RequestBody Map<String, Object> params) {
// 根据分页参数获取对应数据
String key = String.format("%s::*", CacheConstants.PROJECT_OAUTH_ACCESS);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ public final class FormIdentityLoginConfigurer
public void init(HttpSecurity http) throws Exception {
http.formLogin(formLogin -> {
formLogin.loginPage("/token/login");
formLogin.loginProcessingUrl("/token/form");
formLogin.loginProcessingUrl("/oauth2/form");
formLogin.failureHandler(new FormAuthenticationFailureHandler());

})
.logout(logout -> logout.logoutSuccessHandler(new SsoLogoutSuccessHandler())
.logout(logout -> logout.logoutUrl("/oauth2/logout")
.logoutSuccessHandler(new SsoLogoutSuccessHandler())
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)) // SSO登出成功处理

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
return;
}

// 客户端配置跳过验证码
// 如果是密码模式 && 客户端不需要校验验证码
boolean isIgnoreClient = authSecurityConfigProperties.getIgnoreClients().contains(WebUtils.getClientId());
if (isIgnoreClient) {
if (StrUtil.equals(SecurityConstants.PASSWORD, grantType) && isIgnoreClient) {
filterChain.doFilter(request, response);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import java.io.IOException;

/**
* @author lengleng
* @date 2022-06-02
Expand All @@ -46,9 +48,21 @@ public class FormAuthenticationFailureHandler implements AuthenticationFailureHa
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) {
log.debug("表单登录失败:{}", exception.getLocalizedMessage());
String url = HttpUtil.encodeParams(String.format("/token/login?error=%s", exception.getMessage()),

// 获取当前请求的context-path
String contextPath = request.getContextPath();

// 构建重定向URL,加入context-path
String url = HttpUtil.encodeParams(
String.format("%s/token/login?error=%s", contextPath, exception.getMessage()),
CharsetUtil.CHARSET_UTF_8);
WebUtils.getResponse().sendRedirect(url);

try {
WebUtils.getResponse().sendRedirect(url);
}
catch (IOException e) {
log.error("重定向失败", e);
}
}

}
Loading

0 comments on commit 90fac31

Please sign in to comment.