From 1ee4f97ec29ec8083e84f492232bea6fff9efff5 Mon Sep 17 00:00:00 2001 From: wangjianhong <546732225seven@gmail.com> Date: Mon, 14 Jul 2025 08:33:27 +0800 Subject: [PATCH] update --- pom.xml | 33 +++- .../StandaloneServerApplication.java | 4 + .../AuthorizationServerAutoConfigurer.java | 4 +- .../config/SecurityWebAutoConfigurer.java | 30 +-- .../controller/AuthorizationController.java | 36 +--- .../domain/request/LoginRequest.java | 11 -- .../domain/response/BasicUser.java | 14 -- .../domain/response/RestResponse.java | 37 ---- .../authorization/domain/response/Token.java | 13 -- .../filter/JwtRequestFilter.java | 8 +- .../AuthorizationServerProperties.java | 60 ------ .../properties/SecurityWebProperties.java | 89 --------- .../service/AuthorizationService.java | 15 -- .../service/OAuth2ConsentService.java | 72 ++++++- .../impl/AuthorizationServiceImpl.java | 41 ---- .../impl/OAuth2ConsentServiceImpl.java | 71 ------- .../OAuth2AuthorizationServiceStore.java | 2 +- .../store/redis/RedisTokenService.java | 2 +- .../authorization/util/JwtUtils.java | 183 ------------------ src/main/resources/application.yml | 18 +- 20 files changed, 125 insertions(+), 618 deletions(-) delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/domain/request/LoginRequest.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/domain/response/BasicUser.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/domain/response/RestResponse.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/domain/response/Token.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/properties/AuthorizationServerProperties.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/properties/SecurityWebProperties.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/service/AuthorizationService.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/service/impl/AuthorizationServiceImpl.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/service/impl/OAuth2ConsentServiceImpl.java delete mode 100644 src/main/java/com/arrokoth/standalone/authorization/util/JwtUtils.java diff --git a/pom.xml b/pom.xml index 402f531..5295f86 100644 --- a/pom.xml +++ b/pom.xml @@ -37,6 +37,18 @@ + + + com.arrokoth.framework + basic-authorization-server + 1.0.1-RELEASE + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot spring-boot-configuration-processor @@ -56,6 +68,18 @@ org.springframework.boot spring-boot-starter-oauth2-authorization-server + + + nimbus-jose-jwt + com.nimbusds + + + + + + + com.mysql + mysql-connector-j @@ -70,7 +94,7 @@ org.apache.commons commons-pool2 - 2.11.1 + 2.12.1 @@ -84,13 +108,6 @@ test - - - com.github.xiaoymin - knife4j-openapi3-jakarta-spring-boot-starter - 4.4.0 - - org.springframework.session diff --git a/src/main/java/com/arrokoth/standalone/authorization/StandaloneServerApplication.java b/src/main/java/com/arrokoth/standalone/authorization/StandaloneServerApplication.java index df45f88..31c2553 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/StandaloneServerApplication.java +++ b/src/main/java/com/arrokoth/standalone/authorization/StandaloneServerApplication.java @@ -1,8 +1,12 @@ package com.arrokoth.standalone.authorization; +import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +@MapperScan("com.arrokoth.basic.mapper") +@ComponentScan(basePackages = "com.arrokoth") @SpringBootApplication public class StandaloneServerApplication { diff --git a/src/main/java/com/arrokoth/standalone/authorization/config/AuthorizationServerAutoConfigurer.java b/src/main/java/com/arrokoth/standalone/authorization/config/AuthorizationServerAutoConfigurer.java index 190f897..ae6dcd3 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/config/AuthorizationServerAutoConfigurer.java +++ b/src/main/java/com/arrokoth/standalone/authorization/config/AuthorizationServerAutoConfigurer.java @@ -1,7 +1,7 @@ package com.arrokoth.standalone.authorization.config; -import com.arrokoth.standalone.authorization.properties.AuthorizationServerProperties; -import com.arrokoth.standalone.authorization.properties.SecurityWebProperties; +import com.arrokoth.basic.properties.AuthorizationServerProperties; +import com.arrokoth.basic.properties.SecurityWebProperties; import lombok.AllArgsConstructor; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; diff --git a/src/main/java/com/arrokoth/standalone/authorization/config/SecurityWebAutoConfigurer.java b/src/main/java/com/arrokoth/standalone/authorization/config/SecurityWebAutoConfigurer.java index c85d61d..18e52c0 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/config/SecurityWebAutoConfigurer.java +++ b/src/main/java/com/arrokoth/standalone/authorization/config/SecurityWebAutoConfigurer.java @@ -1,10 +1,12 @@ package com.arrokoth.standalone.authorization.config; + +import com.arrokoth.basic.autoconfiguration.BasicAutoConfiguration; +import com.arrokoth.basic.properties.SecurityWebProperties; import com.arrokoth.standalone.authorization.filter.JwtRequestFilter; -import com.arrokoth.standalone.authorization.properties.AuthorizationServerProperties; -import com.arrokoth.standalone.authorization.properties.SecurityWebProperties; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -25,34 +27,12 @@ import java.util.List; @Slf4j @Configuration +@AutoConfigureBefore(BasicAutoConfiguration.class) @AllArgsConstructor @EnableConfigurationProperties(SecurityWebProperties.class) public class SecurityWebAutoConfigurer { private final SecurityWebProperties securityWebProperties; - private final AuthorizationServerProperties authorizationServerProperties; - -// @Bean -// public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { -// http -// .securityMatcher("/**") -// .authorizeHttpRequests(auth -> auth -// .requestMatchers(HttpMethod.OPTIONS).permitAll() -// .requestMatchers(securityWebProperties.getMergedPermittedUrls().toArray(new String[0])).permitAll() -// .anyRequest().authenticated() -// ) -// .formLogin(form -> form -// .loginPage(authorizationServerProperties.getLoginPage()) -// .permitAll() -// ) -// .logout(logout -> logout -// .logoutSuccessUrl(securityWebProperties.getLogoutSuccessUrl()) -// .permitAll() -// ); -// -// return http.build(); -// } - private final JwtRequestFilter jwtRequestFilter; // 你的 Token 过滤器 diff --git a/src/main/java/com/arrokoth/standalone/authorization/controller/AuthorizationController.java b/src/main/java/com/arrokoth/standalone/authorization/controller/AuthorizationController.java index 3a46ff3..d1ca468 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/controller/AuthorizationController.java +++ b/src/main/java/com/arrokoth/standalone/authorization/controller/AuthorizationController.java @@ -1,19 +1,11 @@ package com.arrokoth.standalone.authorization.controller; -import com.arrokoth.standalone.authorization.domain.request.LoginRequest; -import com.arrokoth.standalone.authorization.domain.response.BasicUser; -import com.arrokoth.standalone.authorization.domain.response.RestResponse; -import com.arrokoth.standalone.authorization.domain.response.Token; -import com.arrokoth.standalone.authorization.properties.SecurityWebProperties; -import com.arrokoth.standalone.authorization.service.AuthorizationService; import com.arrokoth.standalone.authorization.service.OAuth2ConsentService; -import com.arrokoth.standalone.authorization.util.JwtUtils; -import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; import java.security.Principal; @@ -23,7 +15,7 @@ import java.security.Principal; public class AuthorizationController { - private final AuthorizationService authorizationService; + private final OAuth2ConsentService oAuth2ConsentService; @@ -32,30 +24,8 @@ public class AuthorizationController { return "login"; // 对应templates/login.html } - @PostMapping(SecurityWebProperties.AXIOS_LOGIN_PROCESSING_URL) - @ResponseBody - public RestResponse axiosLogin(@RequestBody LoginRequest loginRequest) { - return RestResponse.success(authorizationService.login(loginRequest)); - } - - - @GetMapping("/user/info") - @ResponseBody - public RestResponse getBasicUser() { - return RestResponse.success(authorizationService.getBasicUser()); - } - - - @PostMapping("/logout") - @ResponseBody - public ResponseEntity logout(HttpServletRequest request) throws Exception { - String token = JwtUtils.extractTokenFromHeader(request); - authorizationService.logout(JwtUtils.extractJti(token)); - return ResponseEntity.ok("Logged out successfully"); - } - @GetMapping(value = "/oauth2/consent") public String consent(Principal principal, Model model, @RequestParam("client_id") String clientId, diff --git a/src/main/java/com/arrokoth/standalone/authorization/domain/request/LoginRequest.java b/src/main/java/com/arrokoth/standalone/authorization/domain/request/LoginRequest.java deleted file mode 100644 index 66efa4f..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/domain/request/LoginRequest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.arrokoth.standalone.authorization.domain.request; - -import lombok.Data; - -@Data -public class LoginRequest { - - - private String username; - private String password; -} diff --git a/src/main/java/com/arrokoth/standalone/authorization/domain/response/BasicUser.java b/src/main/java/com/arrokoth/standalone/authorization/domain/response/BasicUser.java deleted file mode 100644 index e49fd4c..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/domain/response/BasicUser.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.arrokoth.standalone.authorization.domain.response; - -import lombok.Data; - -import java.util.List; - -@Data -public class BasicUser { - - private String username; - private String avatar; - private String introduction; - private List roles; -} diff --git a/src/main/java/com/arrokoth/standalone/authorization/domain/response/RestResponse.java b/src/main/java/com/arrokoth/standalone/authorization/domain/response/RestResponse.java deleted file mode 100644 index 9752c5f..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/domain/response/RestResponse.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.arrokoth.standalone.authorization.domain.response; - -import lombok.Data; - -@Data -public class RestResponse { - - private String code; - private String message; - private Long timestamp; - private T data; - - - - // 默认构造函数 - public RestResponse() { - this.timestamp = System.currentTimeMillis(); - } - - // 全参构造函数 - public RestResponse(String code, String message, T data) { - this.code = code; - this.message = message; - this.data = data; - this.timestamp = System.currentTimeMillis(); - } - - // 为了方便使用,可以添加一些静态方法来创建实例 - public static RestResponse success(T data) { - return new RestResponse<>("200", "success", data); - } - - public static RestResponse error(String code, String message) { - return new RestResponse<>(code, message, null); - } - -} diff --git a/src/main/java/com/arrokoth/standalone/authorization/domain/response/Token.java b/src/main/java/com/arrokoth/standalone/authorization/domain/response/Token.java deleted file mode 100644 index ac15319..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/domain/response/Token.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.arrokoth.standalone.authorization.domain.response; - -import lombok.Data; - -@Data -public class Token { - - private String access_token; - private String token_type; - private Long expires_in; - private String refresh_token; - -} diff --git a/src/main/java/com/arrokoth/standalone/authorization/filter/JwtRequestFilter.java b/src/main/java/com/arrokoth/standalone/authorization/filter/JwtRequestFilter.java index 4b385f9..874870a 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/filter/JwtRequestFilter.java +++ b/src/main/java/com/arrokoth/standalone/authorization/filter/JwtRequestFilter.java @@ -1,7 +1,7 @@ package com.arrokoth.standalone.authorization.filter; +import com.arrokoth.basic.util.JwtUtils; import com.arrokoth.standalone.authorization.store.redis.RedisTokenService; -import com.arrokoth.standalone.authorization.util.JwtUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -51,9 +51,9 @@ public class JwtRequestFilter extends OncePerRequestFilter { UserDetails userDetails = userDetailsService.loadUserByUsername(username); - if (JwtUtils.validateToken(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - userDetails, null, userDetails.getAuthorities()); + if (JwtUtils.validateToken(jwt, userDetails.getUsername())) { + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } } diff --git a/src/main/java/com/arrokoth/standalone/authorization/properties/AuthorizationServerProperties.java b/src/main/java/com/arrokoth/standalone/authorization/properties/AuthorizationServerProperties.java deleted file mode 100644 index 69d7485..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/properties/AuthorizationServerProperties.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.arrokoth.standalone.authorization.properties; - - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * 授权服务器相关配置属性 - */ -@Data -@ConfigurationProperties(prefix = "arrokoth.authorization.server") -public class AuthorizationServerProperties { - - private static final String DEFAULT_LOGIN_PAGE = "/login"; - private static final String DEFAULT_LOGOUT_SUCCESS_URL = "/login?logout"; - private static final List DEFAULT_PERMIT_URLS = Arrays.asList( - "/login", "/logout", "/connect/logout", - "/assets/**", "/static/**", "/webjars/**", "/error", "/oauth2/**" - ); - /** - * 授权确认页面路径,默认为 "/oauth2/consent" - */ - private String consentPage = "/oauth2/consent"; - /** - * OAuth2 授权端点路径,默认为 "/oauth2/authorize" - */ - private String authorizationEndpoint = "/oauth2/authorize"; - /** - * JWT Issuer 值,默认为 "https://www.arrokoth-info.com" - */ - private String issuer = "https://www.arrokoth-info.com"; - /** - * 登出成功后的跳转 URL。 - * 默认值为 {@link #DEFAULT_LOGOUT_SUCCESS_URL},即 "/login?logout"。 - * 如果在 application.yml 中配置了 app.security.logout-success-url,则使用配置值; - * 否则使用该默认值。 - */ - private String logoutSuccessUrl = DEFAULT_LOGOUT_SUCCESS_URL; - /** - * 需要放行(无需认证即可访问)的 URL 列表。 - * 默认为空列表,由 {@link #getMergedPermittedUrls()} 方法结合默认值进行合并处理。 - */ - private List permitUrls = new ArrayList<>(); - - - /** - * 合并默认值与自定义配置,并去重 - */ - public List getMergedPermittedUrls() { - return Stream.concat(DEFAULT_PERMIT_URLS.stream(), permitUrls.stream()) - .distinct() - .collect(Collectors.toList()); - } -} \ No newline at end of file diff --git a/src/main/java/com/arrokoth/standalone/authorization/properties/SecurityWebProperties.java b/src/main/java/com/arrokoth/standalone/authorization/properties/SecurityWebProperties.java deleted file mode 100644 index 819d1ca..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/properties/SecurityWebProperties.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.arrokoth.standalone.authorization.properties; - - -import cn.hutool.json.JSONUtil; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * 授权服务器相关配置属性 - */ -@Slf4j -@Data -@Component -@ConfigurationProperties(prefix = "arrokoth.security.web") -public class SecurityWebProperties { - - public static final String AXIOS_LOGIN_PROCESSING_URL = "/home/login"; - - - public static final String DEFAULT_LOGIN_PROCESSING_URL = "/web/login"; - - private static final String DEFAULT_LOGOUT_SUCCESS_URL = "/login?logout"; - - private static final String DEFAULT_LOGOUT_PAGE = "/login"; - - - private static final List DEFAULT_PERMIT_URLS = Arrays.asList( - DEFAULT_LOGIN_PROCESSING_URL, - "/login", "/logout", "/connect/logout", "/home/login", - "/assets/**", "/static/**", "/webjars/**", - "/actuator/**", - "/error", "/oauth2/**" - ); - - - private static final List SWAGGER_URLS = Arrays.asList( - "/swagger-ui/**", - "/v3/api-docs/**", - "/swagger-resources/**", - "/webjars/**", - "/doc.html", - "/swagger-ui.html" - ); - - - /** - * 登出成功后的跳转 URL。 - * 默认值为 {@link #DEFAULT_LOGOUT_SUCCESS_URL},即 "/login?logout"。 - * 如果在 application.yml 中配置了 app.security.logout-success-url,则使用配置值; - * 否则使用该默认值。 - */ - private String logoutSuccessUrl = DEFAULT_LOGOUT_SUCCESS_URL; - - - - private String loginPage = DEFAULT_LOGOUT_PAGE; - - /** - * 处理登录请求 - */ - private String loginProcessingUrl = DEFAULT_LOGIN_PROCESSING_URL; - /** - * 需要放行(无需认证即可访问)的 URL 列表。 - * 默认为空列表,由 {@link #getMergedPermittedUrls()} 方法结合默认值进行合并处理。 - */ - private List permitUrls = new ArrayList<>(); - - - /** - * 合并默认值与自定义配置,并去重 - */ - public List getMergedPermittedUrls() { - List merged = Stream.of(DEFAULT_PERMIT_URLS, SWAGGER_URLS, permitUrls) - .flatMap(List::stream) - .distinct() - .collect(Collectors.toList()); - - log.info("Merged permitted urls: {}", JSONUtil.toJsonStr(merged)); - return merged; - } -} \ No newline at end of file diff --git a/src/main/java/com/arrokoth/standalone/authorization/service/AuthorizationService.java b/src/main/java/com/arrokoth/standalone/authorization/service/AuthorizationService.java deleted file mode 100644 index b55d394..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/service/AuthorizationService.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.arrokoth.standalone.authorization.service; - -import com.arrokoth.standalone.authorization.domain.request.LoginRequest; -import com.arrokoth.standalone.authorization.domain.response.BasicUser; -import com.arrokoth.standalone.authorization.domain.response.Token; - -public interface AuthorizationService { - - - Token login(LoginRequest loginRequest); - - void logout(String jti); - - BasicUser getBasicUser(); -} diff --git a/src/main/java/com/arrokoth/standalone/authorization/service/OAuth2ConsentService.java b/src/main/java/com/arrokoth/standalone/authorization/service/OAuth2ConsentService.java index 1d8e7a2..13256bc 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/service/OAuth2ConsentService.java +++ b/src/main/java/com/arrokoth/standalone/authorization/service/OAuth2ConsentService.java @@ -1,17 +1,70 @@ package com.arrokoth.standalone.authorization.service; +import lombok.RequiredArgsConstructor; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import java.security.Principal; -import java.util.Set; +import java.util.*; -public interface OAuth2ConsentService { +/** + * OAuth2 授权确认服务实现类 + */ +@Service +@RequiredArgsConstructor +public class OAuth2ConsentService { - OAuth2ConsentService.ConsentResponse getConsentDetails(Principal principal, - String clientId, - String scope, - String state, - String principalName); + private final RegisteredClientRepository registeredClientRepository; + private final OAuth2AuthorizationConsentService authorizationConsentService; + + /** + * 获取用户授权的详细信息,包括需要审批和已审批的 scopes + * + * @param clientId 客户端 ID + * @param scope 请求的 scope 列表(空格分隔) + * @param principalName 用户名 + * @return 包含待批准和已批准 scopes 的 ConsentResult 对象 + */ + public ConsentResponse getConsentDetails(Principal principal, + String clientId, + String scope, + String state, + String principalName) { + Set scopesToApprove = new HashSet<>(); + Set previouslyApprovedScopes = new HashSet<>(); + + RegisteredClient registeredClient = Optional.ofNullable( + registeredClientRepository.findByClientId(clientId)) + .orElseThrow(() -> new IllegalArgumentException("客户端不存在")); + + OAuth2AuthorizationConsent currentAuthorizationConsent = + this.authorizationConsentService.findById(registeredClient.getId(), principalName); + + Set authorizedScopes = Optional.ofNullable(currentAuthorizationConsent) + .map(OAuth2AuthorizationConsent::getScopes) + .orElse(Set.of()); + + Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")) + .filter(requestedScope -> !OidcScopes.OPENID.equals(requestedScope)) + .forEach(requestedScope -> { + if (authorizedScopes.contains(requestedScope)) { + previouslyApprovedScopes.add(requestedScope); + } else { + scopesToApprove.add(requestedScope); + } + }); + + return new ConsentResponse( + Collections.unmodifiableSet(scopesToApprove), + Collections.unmodifiableSet(previouslyApprovedScopes), + registeredClient + ); + } /** @@ -21,10 +74,11 @@ public interface OAuth2ConsentService { * @param previouslyApprovedScopes 已经授权过的 scopes * @param registeredClient 客户端注册信息 */ - record ConsentResponse( + public record ConsentResponse( Set scopesToApprove, Set previouslyApprovedScopes, RegisteredClient registeredClient ) { } -} + +} \ No newline at end of file diff --git a/src/main/java/com/arrokoth/standalone/authorization/service/impl/AuthorizationServiceImpl.java b/src/main/java/com/arrokoth/standalone/authorization/service/impl/AuthorizationServiceImpl.java deleted file mode 100644 index ce626f4..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/service/impl/AuthorizationServiceImpl.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.arrokoth.standalone.authorization.service.impl; - -import com.arrokoth.standalone.authorization.domain.request.LoginRequest; -import com.arrokoth.standalone.authorization.domain.response.BasicUser; -import com.arrokoth.standalone.authorization.domain.response.Token; -import com.arrokoth.standalone.authorization.service.AuthorizationService; -import com.arrokoth.standalone.authorization.util.JwtUtils; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@Service -public class AuthorizationServiceImpl implements AuthorizationService { - - @Override - public Token login(LoginRequest loginRequest) { - String admin = JwtUtils.createAccessToken("admin"); - - Token token = new Token(); - token.setAccess_token(admin); - token.setExpires_in(JwtUtils.DEFAULT_EXPIRATION); - return token; - } - - @Override - public void logout(String jti) { - - } - - @Override - public BasicUser getBasicUser() { - BasicUser basicUser = new BasicUser(); - basicUser.setUsername("admin"); - basicUser.setAvatar("https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"); - basicUser.setIntroduction("I am a super administrator..."); - basicUser.setRoles(List.of("admin")); - return basicUser; - } -} diff --git a/src/main/java/com/arrokoth/standalone/authorization/service/impl/OAuth2ConsentServiceImpl.java b/src/main/java/com/arrokoth/standalone/authorization/service/impl/OAuth2ConsentServiceImpl.java deleted file mode 100644 index ca436c5..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/service/impl/OAuth2ConsentServiceImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.arrokoth.standalone.authorization.service.impl; - -import com.arrokoth.standalone.authorization.service.OAuth2ConsentService; -import lombok.RequiredArgsConstructor; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; - -import java.security.Principal; -import java.util.*; - -/** - * OAuth2 授权确认服务实现类 - */ -@Service -@RequiredArgsConstructor -public class OAuth2ConsentServiceImpl implements OAuth2ConsentService { - - private final RegisteredClientRepository registeredClientRepository; - private final OAuth2AuthorizationConsentService authorizationConsentService; - - /** - * 获取用户授权的详细信息,包括需要审批和已审批的 scopes - * - * @param clientId 客户端 ID - * @param scope 请求的 scope 列表(空格分隔) - * @param principalName 用户名 - * @return 包含待批准和已批准 scopes 的 ConsentResult 对象 - */ - public ConsentResponse getConsentDetails(Principal principal, - String clientId, - String scope, - String state, - String principalName) { - Set scopesToApprove = new HashSet<>(); - Set previouslyApprovedScopes = new HashSet<>(); - - RegisteredClient registeredClient = Optional.ofNullable( - registeredClientRepository.findByClientId(clientId)) - .orElseThrow(() -> new IllegalArgumentException("客户端不存在")); - - OAuth2AuthorizationConsent currentAuthorizationConsent = - this.authorizationConsentService.findById(registeredClient.getId(), principalName); - - Set authorizedScopes = Optional.ofNullable(currentAuthorizationConsent) - .map(OAuth2AuthorizationConsent::getScopes) - .orElse(Set.of()); - - Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")) - .filter(requestedScope -> !OidcScopes.OPENID.equals(requestedScope)) - .forEach(requestedScope -> { - if (authorizedScopes.contains(requestedScope)) { - previouslyApprovedScopes.add(requestedScope); - } else { - scopesToApprove.add(requestedScope); - } - }); - - return new ConsentResponse( - Collections.unmodifiableSet(scopesToApprove), - Collections.unmodifiableSet(previouslyApprovedScopes), - registeredClient - ); - } - - -} \ No newline at end of file diff --git a/src/main/java/com/arrokoth/standalone/authorization/store/OAuth2AuthorizationServiceStore.java b/src/main/java/com/arrokoth/standalone/authorization/store/OAuth2AuthorizationServiceStore.java index d26241b..5b87fc8 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/store/OAuth2AuthorizationServiceStore.java +++ b/src/main/java/com/arrokoth/standalone/authorization/store/OAuth2AuthorizationServiceStore.java @@ -10,7 +10,7 @@ public class OAuth2AuthorizationServiceStore { @Bean - public OAuth2AuthorizationService authorizationService() { + public OAuth2AuthorizationService oAuth2AuthorizationService() { // 基于db的oauth2认证服务,还有一个基于内存的服务实现InMemoryOAuth2AuthorizationService return new InMemoryOAuth2AuthorizationService(); } diff --git a/src/main/java/com/arrokoth/standalone/authorization/store/redis/RedisTokenService.java b/src/main/java/com/arrokoth/standalone/authorization/store/redis/RedisTokenService.java index 0cf919d..003a8fa 100644 --- a/src/main/java/com/arrokoth/standalone/authorization/store/redis/RedisTokenService.java +++ b/src/main/java/com/arrokoth/standalone/authorization/store/redis/RedisTokenService.java @@ -1,6 +1,6 @@ package com.arrokoth.standalone.authorization.store.redis; -import com.arrokoth.standalone.authorization.util.JwtUtils; +import com.arrokoth.basic.util.JwtUtils; import com.nimbusds.jose.JOSEException; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; diff --git a/src/main/java/com/arrokoth/standalone/authorization/util/JwtUtils.java b/src/main/java/com/arrokoth/standalone/authorization/util/JwtUtils.java deleted file mode 100644 index 45c022f..0000000 --- a/src/main/java/com/arrokoth/standalone/authorization/util/JwtUtils.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.arrokoth.standalone.authorization.util; - -import com.nimbusds.jose.JOSEException; -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.crypto.MACSigner; -import com.nimbusds.jose.crypto.MACVerifier; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.SignedJWT; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.security.core.userdetails.UserDetails; - -import java.security.SecureRandom; -import java.text.ParseException; -import java.util.Base64; -import java.util.Date; -import java.util.UUID; - -public class JwtUtils { - - // ================== 配置常量 ================== - private static final String ISSUER = "your-issuer"; - public static final Long DEFAULT_EXPIRATION = 3600000L; // 默认有效期 1小时 (ms) - private static final String SECRET_KEY = generateSecureSecret(); - - // ================== 公共方法 ================== - - /** - * 创建一个新的访问 Token - * - * @param username 用户标识(如 username 或 user id) - * @return JWT Token 字符串 - */ - public static String createAccessToken(String username) { - try { - return createAccessToken(username, username, DEFAULT_EXPIRATION); - } catch (JOSEException e) { - throw new RuntimeException(e); - } - } - - /** - * 创建一个新的访问 Token,并指定过期时间 - * - * @param subject 用户标识 - * @param expiration 过期时间(毫秒) - * @return JWT Token 字符串 - */ - public static String createAccessToken(String subject, String username, Long expiration) throws JOSEException { - JWTClaimsSet claimsSet = new JWTClaimsSet.Builder() - .subject(subject) - .issuer(ISSUER) - .issueTime(new Date()) - .expirationTime(new Date(System.currentTimeMillis() + expiration)) - .jwtID(UUID.randomUUID().toString()) - .claim("username", username) // 自定义字段存 username - .build(); - - JWSHeader header = new JWSHeader(JWSAlgorithm.HS256); - SignedJWT signedJWT = new SignedJWT(header, claimsSet); - - MACSigner signer = new MACSigner(SECRET_KEY.getBytes()); - signedJWT.sign(signer); - - return signedJWT.serialize(); - } - - /** - * 解析并验证 Token - * - * @param token 要解析的 JWT 字符串 - * @return JWT 的 Claims - */ - public static JWTClaimsSet parseToken(String token) throws ParseException, JOSEException { - SignedJWT signedJWT = SignedJWT.parse(token); - - MACVerifier verifier = new MACVerifier(SECRET_KEY.getBytes()); - if (!signedJWT.verify(verifier)) { - throw new JOSEException("Invalid signature"); - } - - return signedJWT.getJWTClaimsSet(); - } - - /** - * 提取用户名 - * - * @param token JWT Token 字符串 - * @return 用户名 - */ - public static String extractUsername(String token) throws ParseException, JOSEException { - return parseToken(token).getSubject(); - } - - /** - * 获取 Token 的过期时间 - * - * @param token JWT Token 字符串 - * @return 过期时间 - */ - public static Date extractExpiration(String token) throws ParseException, JOSEException { - return parseToken(token).getExpirationTime(); - } - - /** - * 获取 Token 的 jti (JWT ID) - * - * @param token JWT Token 字符串 - * @return jti 值 - */ - public static String extractJti(String token) throws ParseException, JOSEException { - return parseToken(token).getJWTID(); - } - - - /** - * 判断 Token 是否已过期 - * - * @param token JWT Token 字符串 - * @return 是否有效 - */ - public static boolean isTokenExpired(String token) { - try { - return extractExpiration(token).before(new Date()); - } catch (Exception e) { - return true; // 出错也视为过期 - } - } - - /** - * 验证 Token 是否属于当前用户且未过期 - * - * @param token JWT Token 字符串 - * @param userDetails 用户信息 - * @return 是否有效 - */ - public static boolean validateToken(String token, UserDetails userDetails) { - try { - String username = extractUsername(token); - return username.equals(userDetails.getUsername()) && !isTokenExpired(token); - } catch (Exception e) { - return false; - } - } - - // ================== 私有工具方法 ================== - - /** - * 生成安全的密钥(Base64 编码的 256 位密钥) - */ - private static String generateSecureSecret() { - byte[] key = new byte[32]; // 256 bits - new SecureRandom().nextBytes(key); - return Base64.getEncoder().encodeToString(key); - } - - - // 提取 Token 的辅助方法 - public static String extractTokenFromHeader(HttpServletRequest request) { - String header = request.getHeader("Authorization"); - if (header != null && header.startsWith("Bearer ")) { - return header.substring(7); - } - return null; - } - - // ================== 测试入口 ================== - public static void main(String[] args) { - try { - String token = createAccessToken("user@example.com"); - System.out.println("Generated Token: " + token); - - JWTClaimsSet claims = parseToken(token); - System.out.println("Subject: " + claims.getSubject()); - System.out.println("Issuer: " + claims.getIssuer()); - System.out.println("Expiration Time: " + claims.getExpirationTime()); - - System.out.println("Is Valid? " + validateToken(token, null)); - } catch (Exception e) { - e.printStackTrace(); - } - } -} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f5d26c3..3673b40 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,8 +3,15 @@ server: - spring: + aop: + proxy-target-class: true + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/db_base_authorization?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + username: root + password: yyds@8848 + application: name: authorization-server-standalone data: @@ -28,9 +35,18 @@ arrokoth: issuer: https://www.arrokoth-info.com security: web: + swagger-ui: false login-page: /login logout-success-url: /login?logout permit-urls: - /home/login +logging: + level: + root: INFO + com.arrokoth: DEBUG + org.springdoc: INFO + org.springframework: INFO + org.springframework.security: DEBUG + org.springframework.security.oauth2: DEBUG \ No newline at end of file