通用模块开发

This commit is contained in:
haoxin963
2021-06-15 16:51:28 +08:00
parent 6b6a829cb2
commit 082a975d76
16 changed files with 595 additions and 1 deletions

View File

@@ -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";
}

View File

@@ -20,6 +20,11 @@
<artifactId>xtoon-common-core</artifactId>
<version>${xtoon-cloud.version}</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>com.baomidou</groupId>

View File

@@ -15,6 +15,7 @@
<module>xtoon-monitor-server</module>
<module>xtoon-register-server</module>
<module>xtoon-sentinel-server</module>
<module>xtoon-auth-server</module>
</modules>
<properties>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xtoon-ops</artifactId>
<groupId>com.xtoon.cloud</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xtoon-auth-server</artifactId>
<dependencies>
<!-- web环境 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.xtoon.cloud</groupId>
<artifactId>xtoon-common-core</artifactId>
<version>${xtoon-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.xtoon.cloud</groupId>
<artifactId>xtoon-common-web</artifactId>
<version>${xtoon-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.xtoon.cloud</groupId>
<artifactId>xtoon-common-mybatis</artifactId>
<version>${xtoon-cloud.version}</version>
</dependency>
<!--注册中心客户端-->
<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>
<!-- LB 扩展 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- OAuth2 认证服务器-->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -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);
}
}

View File

@@ -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<TokenEnhancer> 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<String, Object> 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;
};
}
}

View File

@@ -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();
};
}
}

View File

@@ -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<String, String> parameters
) throws HttpRequestMethodNotSupportedException {
OAuth2AccessToken oAuth2AccessToken;
oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
return oAuth2AccessToken;
}
}

View File

@@ -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<String, Object> loadPublicKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}

View File

@@ -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<SimpleGrantedAuthority> 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<SimpleGrantedAuthority> 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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: "*"

View File

@@ -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: