diff --git a/db/nacos-mysql.sql b/db/xtoon-nacos.sql similarity index 100% rename from db/nacos-mysql.sql rename to db/xtoon-nacos.sql diff --git a/xtoon-common/xtoon-common-core/src/main/java/com/xtoon/cloud/common/core/constant/AuthConstants.java b/xtoon-common/xtoon-common-core/src/main/java/com/xtoon/cloud/common/core/constant/AuthConstants.java new file mode 100644 index 0000000..afd9698 --- /dev/null +++ b/xtoon-common/xtoon-common-core/src/main/java/com/xtoon/cloud/common/core/constant/AuthConstants.java @@ -0,0 +1,88 @@ +package com.xtoon.cloud.common.core.constant; + +/** + * 认证常量类 + * + * @author haoxin + * @date 2021-06-15 + **/ +public interface AuthConstants { + + + /** + * 认证请求头key + */ + String AUTHORIZATION_KEY = "Authorization"; + + /** + * JWT令牌前缀 + */ + String AUTHORIZATION_PREFIX = "bearer "; + + + /** + * Basic认证前缀 + */ + String BASIC_PREFIX = "Basic "; + + /** + * JWT载体key + */ + String JWT_PAYLOAD_KEY = "payload"; + + /** + * JWT ID 唯一标识 + */ + String JWT_JTI = "jti"; + + /** + * JWT ID 唯一标识 + */ + String JWT_EXP = "exp"; + + /** + * Redis缓存权限规则key + */ + String PERMISSION_ROLES_KEY = "auth:permission:roles"; + + /** + * 黑名单token前缀 + */ + String TOKEN_BLACKLIST_PREFIX = "auth:token:blacklist:"; + + String CLIENT_DETAILS_FIELDS = "client_id, CONCAT('{noop}',client_secret) as client_secret, resource_ids, scope, " + + "authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, " + + "refresh_token_validity, additional_information, autoapprove"; + + String BASE_CLIENT_DETAILS_SQL = "select " + CLIENT_DETAILS_FIELDS + " from oauth_client_details"; + + String FIND_CLIENT_DETAILS_SQL = BASE_CLIENT_DETAILS_SQL + " order by client_id"; + + String SELECT_CLIENT_DETAILS_SQL = BASE_CLIENT_DETAILS_SQL + " where client_id = ?"; + + /** + * 密码加密方式 + */ + String BCRYPT = "{bcrypt}"; + + String USER_ID_KEY = "user_id"; + + String USER_NAME_KEY = "userName"; + + String CLIENT_ID_KEY = "client_id"; + + /** + * JWT存储权限前缀 + */ + String AUTHORITY_PREFIX = "ROLE_"; + + /** + * JWT存储权限属性 + */ + String JWT_AUTHORITIES_KEY = "authorities"; + + + String GRANT_TYPE_KEY = "grant_type"; + + String REFRESH_TOKEN = "refresh_token"; +} diff --git a/xtoon-common/xtoon-common-mybatis/pom.xml b/xtoon-common/xtoon-common-mybatis/pom.xml index f752f55..a3fe37d 100644 --- a/xtoon-common/xtoon-common-mybatis/pom.xml +++ b/xtoon-common/xtoon-common-mybatis/pom.xml @@ -20,6 +20,11 @@ xtoon-common-core ${xtoon-cloud.version} + + + mysql + mysql-connector-java + com.baomidou diff --git a/xtoon-ops/pom.xml b/xtoon-ops/pom.xml index fa17a76..7cbfd62 100644 --- a/xtoon-ops/pom.xml +++ b/xtoon-ops/pom.xml @@ -15,6 +15,7 @@ xtoon-monitor-server xtoon-register-server xtoon-sentinel-server + xtoon-auth-server diff --git a/xtoon-ops/xtoon-auth-server/pom.xml b/xtoon-ops/xtoon-auth-server/pom.xml new file mode 100644 index 0000000..4ca5dcf --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/pom.xml @@ -0,0 +1,62 @@ + + + + xtoon-ops + com.xtoon.cloud + 1.0.0 + + 4.0.0 + + xtoon-auth-server + + + + + org.springframework.boot + spring-boot-starter-web + + + com.xtoon.cloud + xtoon-common-core + ${xtoon-cloud.version} + + + com.xtoon.cloud + xtoon-common-web + ${xtoon-cloud.version} + + + com.xtoon.cloud + xtoon-common-mybatis + ${xtoon-cloud.version} + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + + org.springframework.security.oauth.boot + spring-security-oauth2-autoconfigure + + + org.springframework.security + spring-security-oauth2-jose + + + + \ No newline at end of file diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/XtoonCloudAuthApplication.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/XtoonCloudAuthApplication.java new file mode 100644 index 0000000..2f5726d --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/XtoonCloudAuthApplication.java @@ -0,0 +1,19 @@ +package com.xtoon.cloud.ops.auth; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +/** + * 认证服务 + * + * @author haoxin + * @date 2021-06-15 + **/ +@EnableDiscoveryClient +@SpringBootApplication +public class XtoonCloudAuthApplication { + public static void main(String[] args) { + SpringApplication.run(XtoonCloudAuthApplication.class); + } +} diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/AuthorizationServerConfig.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/AuthorizationServerConfig.java new file mode 100644 index 0000000..e15903c --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/AuthorizationServerConfig.java @@ -0,0 +1,120 @@ +package com.xtoon.cloud.ops.auth.config; + +import com.xtoon.cloud.common.core.constant.AuthConstants; +import com.xtoon.cloud.ops.auth.domain.User; +import com.xtoon.cloud.ops.auth.service.JdbcClientDetailsServiceImpl; +import com.xtoon.cloud.ops.auth.service.UserDetailsServiceImpl; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; +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.token.TokenEnhancer; +import org.springframework.security.oauth2.provider.token.TokenEnhancerChain; +import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter; +import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory; + +import javax.sql.DataSource; +import java.security.KeyPair; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 授权服务配置 + * + * @author haoxin + * @date 2021-06-15 + **/ +@Configuration +@EnableAuthorizationServer +@AllArgsConstructor +public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { + + private DataSource dataSource; + private AuthenticationManager authenticationManager; + private UserDetailsServiceImpl userDetailsService; + + /** + * 配置客户端详情(数据库) + */ + @Override + @SneakyThrows + public void configure(ClientDetailsServiceConfigurer clients) { + JdbcClientDetailsServiceImpl jdbcClientDetailsService = new JdbcClientDetailsServiceImpl(dataSource); + jdbcClientDetailsService.setFindClientDetailsSql(AuthConstants.FIND_CLIENT_DETAILS_SQL); + jdbcClientDetailsService.setSelectClientDetailsSql(AuthConstants.SELECT_CLIENT_DETAILS_SQL); + clients.withClientDetails(jdbcClientDetailsService); + } + + + /** + * 配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services) + */ + @Override + public void configure(AuthorizationServerEndpointsConfigurer endpoints) { + TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain(); + List tokenEnhancers = new ArrayList<>(); + tokenEnhancers.add(tokenEnhancer()); + tokenEnhancers.add(jwtAccessTokenConverter()); + tokenEnhancerChain.setTokenEnhancers(tokenEnhancers); + endpoints + .authenticationManager(authenticationManager) + .accessTokenConverter(jwtAccessTokenConverter()) + .tokenEnhancer(tokenEnhancerChain) + .userDetailsService(userDetailsService) + // refresh token有两种使用方式:重复使用(true)、非重复使用(false),默认为true + // 1 重复使用:access token过期刷新时, refresh token过期时间未改变,仍以初次生成的时间为准 + // 2 非重复使用:access token过期刷新时, refresh token过期时间延续,在refresh token有效期内刷新便永不失效达到无需再次登录的目的 + .reuseRefreshTokens(true); + } + + @Override + public void configure(AuthorizationServerSecurityConfigurer security) { + security.allowFormAuthenticationForClients(); + } + + + /** + * 使用非对称加密算法对token签名 + */ + @Bean + public JwtAccessTokenConverter jwtAccessTokenConverter() { + JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); + converter.setKeyPair(keyPair()); + return converter; + } + + /** + * 从classpath下的密钥库中获取密钥对(公钥+私钥) + */ + @Bean + public KeyPair keyPair() { + KeyStoreKeyFactory factory = new KeyStoreKeyFactory(new ClassPathResource("xtoon.jks"), "123456".toCharArray()); + KeyPair keyPair = factory.getKeyPair("xtoon", "123456".toCharArray()); + return keyPair; + } + + /** + * JWT内容增强 + */ + @Bean + public TokenEnhancer tokenEnhancer() { + return (accessToken, authentication) -> { + Map map = new HashMap<>(8); + User user = (User) authentication.getUserAuthentication().getPrincipal(); + map.put(AuthConstants.USER_ID_KEY, user.getId()); + map.put(AuthConstants.USER_NAME_KEY, user.getUsername()); + ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(map); + return accessToken; + }; + } +} diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/WebSecurityConfig.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/WebSecurityConfig.java new file mode 100644 index 0000000..dc0c541 --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/config/WebSecurityConfig.java @@ -0,0 +1,82 @@ +package com.xtoon.cloud.ops.auth.config; + +import com.google.gson.Gson; +import com.xtoon.cloud.common.web.constant.ResultCode; +import com.xtoon.cloud.common.web.util.Result; +import com.xtoon.cloud.ops.auth.service.UserDetailsServiceImpl; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.AuthenticationEntryPoint; + +/** + * 安全配置 + * + * @author haoxin + * @date 2021-05-29 + **/ +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +@Slf4j +public class WebSecurityConfig extends WebSecurityConfigurerAdapter { + + private final UserDetailsServiceImpl userDetailService; + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() + .and() + .authorizeRequests().antMatchers("/getPublicKey", "/oauth/logout").permitAll() + .antMatchers("/oauth/**", "/webjars/**", "/doc.html", "/swagger-resources/**", "/v2/api-docs").permitAll() + .antMatchers("/error").permitAll() + .anyRequest().authenticated() + .and() + .csrf().disable(); + } + + @Bean + @Override + public AuthenticationManager authenticationManagerBean() throws Exception { + return super.authenticationManagerBean(); + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder()); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + /** + * 自定义认证异常响应数据 + */ + @Bean + public AuthenticationEntryPoint authenticationEntryPoint() { + return (request, response, e) -> { + response.setStatus(HttpStatus.OK.value()); + response.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Cache-Control", "no-cache"); + response.getWriter().print(new Gson().toJson(Result.error(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMsg()))); + response.getWriter().flush(); + }; + } +} + diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/AuthController.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/AuthController.java new file mode 100644 index 0000000..e99c6d4 --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/AuthController.java @@ -0,0 +1,39 @@ +package com.xtoon.cloud.ops.auth.controller; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.oauth2.common.OAuth2AccessToken; +import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; +import java.util.Map; + +/** + * 认证中心 + * + * @author haoxin + * @date 2021-05-29 + **/ +@RestController +@RequestMapping("/oauth") +@AllArgsConstructor +@Slf4j +public class AuthController { + + private TokenEndpoint tokenEndpoint; + + @PostMapping("/token") + public OAuth2AccessToken postAccessToken( + Principal principal, + @RequestParam Map parameters + ) throws HttpRequestMethodNotSupportedException { + OAuth2AccessToken oAuth2AccessToken; + oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody(); + return oAuth2AccessToken; + } +} diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/PublicKeyController.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/PublicKeyController.java new file mode 100644 index 0000000..55600d0 --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/controller/PublicKeyController.java @@ -0,0 +1,36 @@ +package com.xtoon.cloud.ops.auth.controller; + +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.KeyPair; +import java.security.interfaces.RSAPublicKey; +import java.util.Map; + +/** + * 获取公钥接口 + * + * @author haoxin + * @date 2021-05-29 + **/ +@RestController +@RequestMapping +@AllArgsConstructor +@Slf4j +public class PublicKeyController { + + private KeyPair keyPair; + + @GetMapping("/getPublicKey") + public Map loadPublicKey() { + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAKey key = new RSAKey.Builder(publicKey).build(); + return new JWKSet(key).toJSONObject(); + } + +} diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/domain/User.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/domain/User.java new file mode 100644 index 0000000..2132227 --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/domain/User.java @@ -0,0 +1,60 @@ +package com.xtoon.cloud.ops.auth.domain; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; + +/** + * 登录用户信息 + * + * @author haoxin + * @date 2021-06-15 + **/ +@Data +@NoArgsConstructor +public class User implements UserDetails { + + public User(Long id, String username, String password, Boolean enabled, String clientId, Collection authorities) { + this.id = id; + this.username = username; + this.password = password; + this.enabled = enabled; + this.clientId = clientId; + this.authorities = authorities; + } + + private Long id; + + private String username; + + private String password; + + private Boolean enabled; + + private String clientId; + + private Collection authorities; + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return this.enabled; + } +} diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/JdbcClientDetailsServiceImpl.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/JdbcClientDetailsServiceImpl.java new file mode 100644 index 0000000..ecca4da --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/JdbcClientDetailsServiceImpl.java @@ -0,0 +1,26 @@ +package com.xtoon.cloud.ops.auth.service; + +import lombok.SneakyThrows; +import org.springframework.security.oauth2.provider.ClientDetails; +import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService; + +import javax.sql.DataSource; + +/** + * 客户端信息配置 + * + * @author haoxin + * @date 2021-06-15 + **/ +public class JdbcClientDetailsServiceImpl extends JdbcClientDetailsService { + + public JdbcClientDetailsServiceImpl(DataSource dataSource) { + super(dataSource); + } + + @Override + @SneakyThrows + public ClientDetails loadClientByClientId(String clientId) { + return super.loadClientByClientId(clientId); + } +} diff --git a/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/UserDetailsServiceImpl.java b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/UserDetailsServiceImpl.java new file mode 100644 index 0000000..d454a6c --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/java/com/xtoon/cloud/ops/auth/service/UserDetailsServiceImpl.java @@ -0,0 +1,27 @@ +package com.xtoon.cloud.ops.auth.service; + +import com.xtoon.cloud.ops.auth.domain.User; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +/** + * 自定义用户认证和授权 + * + * @author haoxin + * @date 2021-05-29 + **/ +@Service +@AllArgsConstructor +@Slf4j +public class UserDetailsServiceImpl implements UserDetailsService { + + @Override + public User loadUserByUsername(String s) throws UsernameNotFoundException { + // TODO test + return new User(1L,"test", new BCryptPasswordEncoder().encode("123456"), true,"client",null); + } +} diff --git a/xtoon-ops/xtoon-auth-server/src/main/resources/bootstrap.yml b/xtoon-ops/xtoon-auth-server/src/main/resources/bootstrap.yml new file mode 100644 index 0000000..732efce --- /dev/null +++ b/xtoon-ops/xtoon-auth-server/src/main/resources/bootstrap.yml @@ -0,0 +1,29 @@ +spring: + application: + name: xtoon-auth-server + cloud: + nacos: + config: + server-addr: localhost:8848 + file-extension: yaml + enabled: true + boot: + admin: + client: + url: http://localhost:5001 + username: admin + password: admin + instance: + prefer-ip: true + +management: + health: + redis: + enabled: false + endpoint: + health: + show-details: always + endpoints: + web: + exposure: + include: "*" \ No newline at end of file diff --git a/xtoon-ops/xtoon-auth-server/src/main/resources/xtoon.jks b/xtoon-ops/xtoon-auth-server/src/main/resources/xtoon.jks new file mode 100644 index 0000000..746a4b2 Binary files /dev/null and b/xtoon-ops/xtoon-auth-server/src/main/resources/xtoon.jks differ diff --git a/xtoon-ops/xtoon-register-server/src/main/resources/bootstrap.yml b/xtoon-ops/xtoon-register-server/src/main/resources/bootstrap.yml index 029b031..d093ddd 100644 --- a/xtoon-ops/xtoon-register-server/src/main/resources/bootstrap.yml +++ b/xtoon-ops/xtoon-register-server/src/main/resources/bootstrap.yml @@ -8,7 +8,7 @@ db: user: root password: haoxin963 url: - 0: jdbc:mysql://localhost:3306/xtoon-cloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai + 0: jdbc:mysql://localhost:3306/xtoon-nacos?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai nacos: