springcloud微服务以及oauth2认证服务器。注册中心使用eureka,网关服务getaway,内部服务调用使用fegin。
提示:以下是本篇文章正文内容,下面案例可供参考
主要包含了oauth认证服务;common公共依赖包放了一些共用工具,Redis,ID生成工具,json工具等等;以及注册中心eureka和网关getaway。另外加了两个微服务,用户服务,以及订单服务。
pom文件如下:
<?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">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-user</module>
<module>springcloud-order</module>
<module>springcloud-common</module>
<module>springcloud-auth</module>
<module>springcloud-eureka</module>
<module>springcloud-getaway</module>
</modules>
<properties>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>2.3.4.RELEASE</spring-boot.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${
spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${
spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.24</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<delimiter>$</delimiter>
</delimiters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${
jdk.version}</source><!-- 源代码使用的开发版本 -->
<target>${
jdk.version}</target><!-- 需要生成的目标class文件的编译版本 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
pom文件如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
pom文件如下:
<?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>springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-common</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
</dependencies>
</project>
pom文件如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>eureka</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
pom文件如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>springcloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>getaway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
pom文件如下:
<?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>springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-order</artifactId>
<modules>
<module>springcloud-order-api</module>
<module>springcloud-order-service</module>
</modules>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-common</artifactId>
</dependency>
</dependencies>
</project>
pom文件如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>springcloud-order</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
pom文件如下:
<?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>springcloud</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-user</artifactId>
<packaging>pom</packaging>
<modules>
<module>springcloud-user-api</module>
<module>springcloud-user-service</module>
</modules>
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>springcloud-common</artifactId>
</dependency>
</dependencies>
</project>
pom文件如下:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.example</groupId>
<artifactId>springcloud-user</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.springcloud</groupId>
<artifactId>user-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//密码加密需要使用到的加密方式
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
//认证管理器
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
//允许匿名访问所有接口,主要是oauth
.antMatchers("/**").permitAll()
//httpBasic认证
.and().httpBasic()
//关闭CSRF
.and().csrf().disable();
//设置session无状态
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
Redis方式存储配置文件。
@Configuration
public class RedisTokenStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore (){
return new RedisTokenStore(redisConnectionFactory);
}
}
JWT方式存储配置文件。
//@Configuration
public class JwtTokenConfig {
// @Bean
// public TokenStore jwtTokenStore() {
// return new JwtTokenStore(jwtAccessTokenConverter());
// }
//
// @Bean
// public JwtAccessTokenConverter jwtAccessTokenConverter() {
// JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
// accessTokenConverter.setSigningKey("springcloud");
// return accessTokenConverter;
// }
}
当前使用的是Redis方式存储token,为了实现同端同用户token互顶。
@Service
public class UserLoginServiceImpl implements UserDetailsService {
@Autowired
private UserLoginMapper userLoginMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//查询用户是否存在
UserLogin userLogin = userLoginMapper.queryUserInfoByMobile(username);
if (ObjectUtils.isEmpty(userLogin))
throw new UsernameNotFoundException("用户名或密码错误");
//是否允许登录
if (userLogin.getDisable() == 1)
throw new UsernameNotFoundException("当前用户禁止登陆");
//有扩展手机号验证码登录,所以校验一下是否设置密码
if (StringUtils.isEmpty(userLogin.getPassword()))
throw new UsernameNotFoundException("未设置登录密码");
// String role = "ROLE_ADMIN";
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// authorities.add(new SimpleGrantedAuthority(role));
return new UserLoginInfo(userLogin.getUserId(),userLogin.getUserName(),userLogin.getPassword(), authorities);
}
}
UserLoginMapper
@Mapper
public interface UserLoginMapper {
UserLogin queryUserInfoByMobile(String username);
void createUserLogin(UserLogin userLogin);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.springcloud.auth.mapper.UserLoginMapper">
<select id="queryUserInfoByMobile" resultType="com.springcloud.auth.entity.UserLogin">
SELECT
user_id as userId,
user_name as userName,
mobile,
password,
disable
FROM
user_login
WHERE
mobile = #{
mobile}
and deleted = 0
</select>
<insert id="createUserLogin" parameterType="com.springcloud.auth.entity.UserLogin">
INSERT INTO user_login (
user_id,
user_name,
mobile
)
VALUES
(
#{
userId},
#{
userName},
#{
mobile}
)
</insert>
</mapper>
UserLogin实体
@Data
public class UserLogin {
//用户id
private Long userId;
//用户名
private String userName;
//手机号码
private String mobile;
//密码
private String password;
//三方登录标识
private String openid;
//是否禁用:0否,1是
private Integer disable;
//创建时间
private Timestamp createTime;
//更新时间
private Timestamp updateTime;
//删除状态:0否,1是
private Integer deleted;
}
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
public PasswordEncoder passwordEncoder;
@Autowired
public UserDetailsService userLoginServiceImpl;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private TokenStore redisTokenStore;
@Autowired
private DataSource dataSource;
// @Autowired
// private TokenStore jwtTokenStore;
//
// @Autowired
// private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private RedisUtils redisUtils;
@Autowired
private UserLoginMapper userLoginMapper;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
JdbcClientDetailsServiceBuilder jdbcClientDetailsServiceBuilder = clients.jdbc(dataSource);
jdbcClientDetailsServiceBuilder.passwordEncoder(passwordEncoder);
}
// @Override
// public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// /**
// * 普通 jwt 模式
// */
// endpoints.tokenStore(jwtTokenStore)
// .accessTokenConverter(jwtAccessTokenConverter)
.tokenServices(singleTokenServices)
// .userDetailsService(userDetailsServiceImpl)
// .tokenGranter(new CompositeTokenGranter(getTokenGranters(endpoints.getClientDetailsService(),endpoints.getTokenServices(),
// endpoints.getAuthorizationCodeServices(),endpoints.getOAuth2RequestFactory())))
// /**
// * 支持 password 模式
// */
// .authenticationManager(authenticationManager);
// }
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
/**
* redis token 方式
*/
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userLoginServiceImpl)
//自定义tokenService实现用户互顶,自定义token
.tokenServices(OnlyTokenService(endpoints))
//自定义tokenGranter增加手机号验证码登录实现
.tokenGranter(new CompositeTokenGranter(getTokenGranters(endpoints.getClientDetailsService(),endpoints.getTokenServices(),
endpoints.getAuthorizationCodeServices(),endpoints.getOAuth2RequestFactory())))
.exceptionTranslator(oauthWebResponseExceptionTranslator())
.tokenStore(redisTokenStore);
}
private List<TokenGranter> getTokenGranters(ClientDetailsService clientDetails, AuthorizationServerTokenServices tokenServices,
AuthorizationCodeServices authorizationCodeServices, OAuth2RequestFactory requestFactory) {
//取AuthorizationServerEndpointsConfigurer的getDefaultTokenGranters()方法
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
//增加手机号验证码登录实现
tokenGranters.add(new MobileSmsTokenGranter(tokenServices, clientDetails, requestFactory,"mobile_sms",redisUtils,userLoginMapper));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
clientDetails, requestFactory));
}
return tokenGranters;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.checkTokenAccess("permitAll()");
security.tokenKeyAccess("isAuthenticated()");
}
//自定义tokenservice,实现自定义token生成方式,实现token互顶
private AuthorizationServerTokenServices OnlyTokenService(AuthorizationServerEndpointsConfigurer endpoints){
CustomTokenServices onlyTokenService = new CustomTokenServices();
onlyTokenService.setTokenStore(redisTokenStore);
onlyTokenService.setSupportRefreshToken(true);
onlyTokenService.setReuseRefreshToken(true);
onlyTokenService.setClientDetailsService(endpoints.getClientDetailsService());
onlyTokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
addUserDetailsService(onlyTokenService,this.userLoginServiceImpl);
return onlyTokenService;
}
private void addUserDetailsService(CustomTokenServices tokenServices, UserDetailsService userDetailsService) {
if (userDetailsService != null) {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(
userDetailsService));
tokenServices.setAuthenticationManager(new ProviderManager(Arrays.asList(provider)));
}
}
@Bean
public CustomExceptionTranslator oauthWebResponseExceptionTranslator(){
return new CustomExceptionTranslator();
}
}
自定义CustomTokenServices,实现token互顶,自定义token。
/**
* 拷贝DefaultTokenServices--主要修改两个createAccessToken实现用户互顶,自定义token
*/
public class CustomTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
ConsumerTokenServices, InitializingBean {
private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.
private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.
private boolean supportRefreshToken = false;
private boolean reuseRefreshToken = true;
private TokenStore tokenStore;
private ClientDetailsService clientDetailsService;
private TokenEnhancer accessTokenEnhancer;
private AuthenticationManager authenticationManager;
/**
* Initialize these token services. If no random generator is set, one will be created.
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(tokenStore, "tokenStore must be set");
}
@Transactional
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
OAuth2RefreshToken refreshToken = null;
if (existingAccessToken != null) {
if (existingAccessToken.getRefreshToken() != null) {
refreshToken = existingAccessToken.getRefreshToken();
// The token store could remove the refresh token when the
// access token is removed, but we want to
// be sure...
tokenStore.removeRefreshToken(refreshToken);
}
tokenStore.removeAccessToken(existingAccessToken);
}
// Only create a new refresh token if there wasn't an existing one
// associated with an expired access token.
// Clients might be holding existing refresh tokens, so we re-use it in
// the case that the old access token
// expired.
if (refreshToken == null) {
refreshToken = createRefreshToken(authentication);
}
// But the refresh token itself might need to be re-issued if it has
// expired.
else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
refreshToken = createRefreshToken(authentication);
}
}
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
// In case it was modified
refreshToken = accessToken.getRefreshToken();
if (refreshToken != null) {
tokenStore.storeRefreshToken(refreshToken, authentication);
}
return accessToken;
}
@Transactional(noRollbackFor={
InvalidTokenException.class, InvalidGrantException.class})
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
throws AuthenticationException {
if (!supportRefreshToken) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
}
OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);
if (refreshToken == null) {
throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);
}
OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
if (this.authenticationManager != null && !authentication.isClientOnly()) {
// The client has already been authenticated, but the user authentication might be old now, so give it a
// chance to re-authenticate.
Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
user = authenticationManager.authenticate(user);
Object details = authentication.getDetails();
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
authentication.setDetails(details);
}
String clientId = authentication.getOAuth2Request().getClientId();
if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);
}
// clear out any access tokens already associated with the refresh
// token.
tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);
if (isExpired(refreshToken)) {
tokenStore.removeRefreshToken(refreshToken);
throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);
}
authentication = createRefreshedAuthentication(authentication, tokenRequest);
if (!reuseRefreshToken) {
tokenStore.removeRefreshToken(refreshToken);
refreshToken = createRefreshToken(authentication);
}
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
if (!reuseRefreshToken) {
tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);
}
return accessToken;
}
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
return tokenStore.getAccessToken(authentication);
}
/**
* Create a refreshed authentication.
*
* @param authentication The authentication.
* @param request The scope for the refreshed token.
* @return The refreshed authentication.
* @throws InvalidScopeException If the scope requested is invalid or wider than the original scope.
*/
private OAuth2Authentication createRefreshedAuthentication(OAuth2Authentication authentication, TokenRequest request) {
OAuth2Authentication narrowed = authentication;
Set<String> scope = request.getScope();
OAuth2Request clientAuth = authentication.getOAuth2Request().refresh(request);
if (scope != null && !scope.isEmpty()) {
Set<String> originalScope = clientAuth.getScope();
if (originalScope == null || !originalScope.containsAll(scope)) {
throw new InvalidScopeException("Unable to narrow the scope of the client authentication to " + scope
+ ".", originalScope);
}
else {
clientAuth = clientAuth.narrowScope(scope);
}
}
narrowed = new OAuth2Authentication(clientAuth, authentication.getUserAuthentication());
return narrowed;
}
protected boolean isExpired(OAuth2RefreshToken refreshToken) {
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;
return expiringToken.getExpiration() == null
|| System.currentTimeMillis() > expiringToken.getExpiration().getTime();
}
return false;
}
public OAuth2AccessToken readAccessToken(String accessToken) {
return tokenStore.readAccessToken(accessToken);
}
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
InvalidTokenException {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
else if (accessToken.isExpired()) {
tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
}
OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
if (result == null) {
// in case of race condition
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
}
if (clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();
try {
clientDetailsService.loadClientByClientId(clientId);
}
catch (ClientRegistrationException e) {
throw new InvalidTokenException("Client not valid: " + clientId, e);
}
}
return result;
}
public String getClientId(String tokenValue) {
OAuth2Authentication authentication = tokenStore.readAuthentication(tokenValue);
if (authentication == null) {
throw new InvalidTokenException("Invalid access token: " + tokenValue);
}
OAuth2Request clientAuth = authentication.getOAuth2Request();
if (clientAuth == null) {
throw new InvalidTokenException("Invalid access token (no client id): " + tokenValue);
}
return clientAuth.getClientId();
}
public boolean revokeToken(String tokenValue) {
OAuth2AccessToken accessToken = tokenStore.readAccessToken(tokenValue);
if (accessToken == null) {
return false;
}
if (accessToken.getRefreshToken() != null) {
tokenStore.removeRefreshToken(accessToken.getRefreshToken());
}
tokenStore.removeAccessToken(accessToken);
return true;
}
private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
return null;
}
int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
String value = UUID.randomUUID().toString();
if (validitySeconds > 0) {
return new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis()
+ (validitySeconds * 1000L)));
}
return new DefaultOAuth2RefreshToken(value);
}
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
Object object = authentication.getUserAuthentication().getPrincipal();
long userId = 0L;
if (object instanceof UserLoginInfo){
userId = ((UserLoginInfo) object).getUserId();
}
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(SecurityUtils.getToken(String.valueOf(userId),UUID.randomUUID().toString()));
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
/**
* The access token validity period in seconds
*
* @param clientAuth the current authorization request
* @return the access token validity period in seconds
*/
protected int getAccessTokenValiditySeconds(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getAccessTokenValiditySeconds();
if (validity != null) {
return validity;
}
}
return accessTokenValiditySeconds;
}
/**
* The refresh token validity period in seconds
*
* @param clientAuth the current authorization request
* @return the refresh token validity period in seconds
*/
protected int getRefreshTokenValiditySeconds(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
Integer validity = client.getRefreshTokenValiditySeconds();
if (validity != null) {
return validity;
}
}
return refreshTokenValiditySeconds;
}
/**
* Is a refresh token supported for this client (or the global setting if
* {@link #setClientDetailsService(ClientDetailsService) clientDetailsService} is not set.
*
* @param clientAuth the current authorization request
* @return boolean to indicate if refresh token is supported
*/
protected boolean isSupportRefreshToken(OAuth2Request clientAuth) {
if (clientDetailsService != null) {
ClientDetails client = clientDetailsService.loadClientByClientId(clientAuth.getClientId());
return client.getAuthorizedGrantTypes().contains("refresh_token");
}
return this.supportRefreshToken;
}
/**
* An access token enhancer that will be applied to a new token before it is saved in the token store.
*
* @param accessTokenEnhancer the access token enhancer to set
*/
public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {
this.accessTokenEnhancer = accessTokenEnhancer;
}
/**
* The validity (in seconds) of the refresh token. If less than or equal to zero then the tokens will be
* non-expiring.
*
* @param refreshTokenValiditySeconds The validity (in seconds) of the refresh token.
*/
public void setRefreshTokenValiditySeconds(int refreshTokenValiditySeconds) {
this.refreshTokenValiditySeconds = refreshTokenValiditySeconds;
}
/**
* The default validity (in seconds) of the access token. Zero or negative for non-expiring tokens. If a client
* details service is set the validity period will be read from the client, defaulting to this value if not defined
* by the client.
*
* @param accessTokenValiditySeconds The validity (in seconds) of the access token.
*/
public void setAccessTokenValiditySeconds(int accessTokenValiditySeconds) {
this.accessTokenValiditySeconds = accessTokenValiditySeconds;
}
/**
* Whether to support the refresh token.
*
* @param supportRefreshToken Whether to support the refresh token.
*/
public void setSupportRefreshToken(boolean supportRefreshToken) {
this.supportRefreshToken = supportRefreshToken;
}
/**
* Whether to reuse refresh tokens (until expired).
*
* @param reuseRefreshToken Whether to reuse refresh tokens (until expired).
*/
public void setReuseRefreshToken(boolean reuseRefreshToken) {
this.reuseRefreshToken = reuseRefreshToken;
}
/**
* The persistence strategy for token storage.
*
* @param tokenStore the store for access and refresh tokens.
*/
public void setTokenStore(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
/**
* An authentication manager that will be used (if provided) to check the user authentication when a token is
* refreshed.
*
* @param authenticationManager the authenticationManager to set
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* The client details service to use for looking up clients (if necessary). Optional if the access token expiry is
* set globally via {@link #setAccessTokenValiditySeconds(int)}.
*
* @param clientDetailsService the client details service
*/
public void setClientDetailsService(ClientDetailsService clientDetailsService) {
this.clientDetailsService = clientDetailsService;
}
}
MobileSmsTokenGranter 自定义手机号验证码登录
@Log4j2
public class MobileSmsTokenGranter extends AbstractTokenGranter {
private UserLoginMapper userLoginMapper;
private RedisUtils redisUtils;
public MobileSmsTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService,
OAuth2RequestFactory requestFactory, String grantType, RedisUtils redisUtils ,UserLoginMapper userLoginMapper) {
super(tokenServices, clientDetailsService, requestFactory, grantType);
this.redisUtils = redisUtils;
this.userLoginMapper = userLoginMapper;
}
@SneakyThrows
@Override
protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {
Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
//手机号
String username = parameters.get("username");
//验证码
String password = parameters.get("password");
// Protect from downstream leaks of password
parameters.remove("password");
//从Redis获取验证码校验
Object code = redisUtils.get("LOGIN" + ":" + username);
if (ObjectUtils.isEmpty(code) || !password.equals(code.toString()))
throw ApiException.restException(ApiError.CODE_ERROR, "oauth-1.0");
//查询用户是否存在
UserLogin userLogin = userLoginMapper.queryUserInfoByMobile(username);
if (!ObjectUtils.isEmpty(userLogin)){
//校验当前用户是否允许登录
if (userLogin.getDisable() == 1)
throw ApiException.restException(ApiError.USER_DISABLE,"oauth-1.0");
}else {
//如果用户不存在则创建用户
userLogin = getUserLogin(username);
userLoginMapper.createUserLogin(userLogin);
}
//用户角色信息
// String role = "ROLE_ADMIN";
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// authorities.add(new SimpleGrantedAuthority(role));
//用户信息
UserLoginInfo principal = new UserLoginInfo(userLogin.getUserId(),username,password,authorities);
AbstractAuthenticationToken userAuth =
new UsernamePasswordAuthenticationToken(principal,null,authorities);
userAuth.setDetails(parameters);
OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);
return new OAuth2Authentication(storedOAuth2Request, userAuth);
}
private UserLogin getUserLogin(String username) {
UserLogin userLogin = new UserLogin();
userLogin.setUserId(SnowflakeIdUtils.newId());
userLogin.setUserName(username);
userLogin.setMobile(username);
return userLogin;
}
自定义oauth2异常响应内容,搜的资料随便弄一下的
/**
* 自定义oauth异常响应内容,搜的资料随便弄一下的
*/
public class CustomExceptionTranslator implements WebResponseExceptionTranslator {
private static final String INVALID_TOKEN_ERROR_DESCRIPTION = "Token was not recognised";//token 无效
private static final String UNSUPPORTED_GRANT_TYPE = "Unsupported grant type";//登录类型错误
private static final String USER_ERROR = "用户名或密码错误";//密码有误
private static final String NO_LOGIN = "当前用户禁止登陆";
private static final String NO_PASSWORD = "未设置登录密码";
@Override
public ResponseEntity translate(Exception e) throws Exception {
System.out.println(e.getMessage());
if (e instanceof ApiException){
return ResponseEntity.ok().body(((ApiException) e).getResponse());
}
if (USER_ERROR.equals(e.getMessage())) {
//用户名密码错误
return ResponseEntity.ok().body(BaseResponse.BuildError(ApiError.USER_ERROR, "oauth-1.0"));
}
if (!StringUtils.isEmpty(e.getMessage()) && e.getMessage().startsWith(UNSUPPORTED_GRANT_TYPE)) {
//登录类型错误
return ResponseEntity.ok().body(BaseResponse.BuildError(ApiError.GRANT_TYPE,"oauth-1.0"));
}
if (!StringUtils.isEmpty(e.getMessage()) && e.getMessage().startsWith(NO_LOGIN)){
return ResponseEntity.ok().body(BaseResponse.BuildError(ApiError.USER_DISABLE,"oauth-1.0"));
}
if (!StringUtils.isEmpty(e.getMessage()) && e.getMessage().startsWith(NO_PASSWORD)){
return ResponseEntity.ok().body(BaseResponse.BuildError(ApiError.NO_PASSWORD,"oauth-1.0"));
}
if (!StringUtils.isEmpty(e.getMessage()) && e.getMessage().equals(INVALID_TOKEN_ERROR_DESCRIPTION)){
//token失效
return ResponseEntity.ok().body(BaseResponse.BuildError(ApiError.TOKEN_ERROR,"oauth-1.0"));
}
//未知异常
return ResponseEntity.ok().body(BaseResponse.BuildError(ApiError.UNKNOWN_ERROR,"oauth-1.0"));
}
}
OauthClientDetails数据库,以及实体,具体不清楚的可以去百度一下,之前在oauth配置里面配置了DataSource,就是让oauth去数据库获取相关的配置。
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`resource_ids` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`client_secret` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`scope` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`authorized_grant_types` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`web_server_redirect_uri` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`authorities` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`access_token_validity` int NULL DEFAULT NULL,
`refresh_token_validity` int NULL DEFAULT NULL,
`additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`autoapprove` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('springcloud-app', 'user-client,oauth2-resource', '$2a$10$qbi8pIHV0aRA7k3y9DaUZOMGnINhQqtHUAK9bFi8h9MN16vvP4b4.', 'user,order', 'authorization_code,refresh_token,password,mobile_sms', 'www.baidu.com', NULL, 36000, 360000, NULL, '1');
SET FOREIGN_KEY_CHECKS = 1;
@Data
public class OauthClientDetails {
//用于唯一标识每一个客户端
private String clientId;
//客户端所能访问的资源id集合,多个资源时用逗号(,)分隔
private String resource_ids;
//用于指定客户端(client)的访问密匙
private String client_secret;
//指定客户端申请的权限范围若有多个权限范围用逗号(,)分隔
private String scope;
//指定客户端支持的grant_type, 若支持多个grant_type用逗号(,)分隔
private String authorized_grant_types;
//回调地址
private String web_server_redirect_uri;
//指定客户端所拥有的Spring Security的权限值,可选, 若有多个权限值,用逗号(,)分隔 如: "ROLE_UNITY,ROLE_USER"
private String authorities;
//设定客户端的access_token的有效时间值(单位:秒)若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时)
private String access_token_validity;
//设定客户端的refresh_token的有效时间值(单位:秒)若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, 30天)
private String refresh_token_validity;
//附加信息,若设置值,必须是JSON格式的数据
private String additional_information;
//设置用户是否自动Approval操作, 默认值为 ''false'', 可选值包括 ''true'',''false'', ''read'',''write''.
private String autoapprove;
//创建时间
private Timestamp createTime;
//更新时间
private Timestamp updateTime;
}
springcloud-auth的application.propproperties
server.port=9999
spring.application.name=springcloud-auth
#Redis配置
spring.redis.database=1
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=200
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=1200ms
#数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.maximum-pool-size=9
#配置mapper路径,开启驼峰转换
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.jdbc-type-for-null=null
common下面都是一些共用的工具类,实体,一些常量,异常类。直接贴了。
@Configuration
public class FeignOauth2RequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
String AUTHORIZATION_HEADER = "Authorization";
String BEARER_TOKEN_TYPE = "Bearer";
requestTemplate.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER_TOKEN_TYPE, details.getTokenValue()));
}
}
}
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
// template.setValueSerializer(jackson2JsonRedisSerializer);
// value序列化方式采用String
template.setValueSerializer(stringRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
public enum ApiError {
UNKNOWN_ERROR(10000, "未知错误,请稍后重试!"),
CODE_ERROR(10001, "验证码已失效,或输入有误!"),
TOKEN_ERROR(10002,"token无效!"),
AUTH_CODE_ERROR(10003,"授权码无效!"),
USER_ERROR(10004,"用户名密码输入有误!"),
GRANT_TYPE(10005,"登录类型有误!"),
USER_NOTHING(10006,"用户不存在!"),
USER_DISABLE(10007,"当前用户禁止登陆,请联系客服处理!"),
NO_PASSWORD(10008,"未设置登录密码!"),
;
ApiError(int code, String msg){
this.code = code;
this.msg = msg;
}
private int code;
private String msg;
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
public final class Constants {
/** 成功返回code */
public final static int NUM_TWO_HUNDRED = 200;
public static final String SUCCESS = "成功";
}
public class SysConfig {
/** 短信验证码过期时间s */
public static final long CODE_EXPIRED = 600;
/** token加密key */
public static final String TOKEN_AES_KEY = "Ge5xLvgP6z1a3Fjp";
/** 用户登录密码错误次数 */
public static final int ERROR_COUNT = 5;
/** 分页默认显示条数*/
public static final int PAGE_SIZE=10;
}
@Data
@Builder
public class ApiException extends Exception{
private BaseResponse<Object> response;
public static ApiException restException(ApiError apiError , String version){
return ApiException.builder().response(BaseResponse.builder().version(version).code(apiError.getCode()).msg(apiError.getMsg()).build()).build();
}
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BaseRequest<T> implements Serializable {
//请求时间戳
private long timestamp;
//请求版本号
private String version;
//请求参数
@Valid
private T parameter;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BaseResponse<T> implements Serializable {
//当前系统版本号
private String version;
//响应时间戳
private long timestamp;
//响应数据
private T result;
//响应码
private int code;
//消息
private String msg;
public static <T> BaseResponse<T> success(T data ,String version) {
return BaseResponse.<T>builder().code(Constants.NUM_TWO_HUNDRED)
.msg(Constants.SUCCESS)
.result(data).version(version)
.timestamp(System.currentTimeMillis()).build();
}
public static <T> BaseResponse<T> BuildError(ApiError error , String version) {
return BaseResponse.<T>builder().code(error.getCode())
.msg(error.getMsg())
.version(version)
.timestamp(System.currentTimeMillis()).build();
}
public static <T> BaseResponse<T> fail(ApiError error ,String version) {
return BaseResponse.<T>builder().code(error.getCode())
.msg(error.getMsg())
.result(null)
.version(version)
.timestamp(System.currentTimeMillis()).build();
}
}
@Component
public final class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 批量获取key
* @param data
* @return
*/
public List<String> getKeys(String data){
Set<String> set=redisTemplate.keys("*"+data+"*");
// 将set转成ArrayList
List<String> list=new ArrayList<>(set);
return list;
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
* @param key 键
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
@Log4j2
public class SecurityUtils {
// 加密
private static String encryptAES(String sSrc) throws Exception {
byte[] raw = SysConfig.TOKEN_AES_KEY.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encrypted = cipher.doFinal(sSrc.getBytes(StandardCharsets.UTF_8));
String token = new Base64().encodeToString(encrypted).replaceAll("\\+","-");//此处使用BASE64做转码功能,同时能起到2次加密的作用。
return token;
}
// 解密
public static String decryptAES(String sSrc, String sKey) throws Exception {
try {
sSrc = sSrc.replaceAll("-","+");
byte[] raw = sKey.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] encrypted1 = new Base64().decode(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
return new String(original, StandardCharsets.UTF_8);
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
/**
* 将 Token 反解
*/
public static Long getTokenUserId(String token) {
Long userId = null;
try {
String res = decryptAES(token, SysConfig.TOKEN_AES_KEY);
if (StringUtils.isEmpty(res))
return userId;
String[] resParam = res.split("\\|");
userId = Long.parseLong(resParam[0]);
} catch (Exception e) {
log.info(e.getMessage());
e.printStackTrace();
}
return userId;
}
/**
* 加密 token
*/
public static String getToken(String userId, String token) {
String toEncrypt = userId + "|" + token;
try {
return encryptAES(toEncrypt);
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
}
/**
* Twitter_Snowflake
* SnowFlake的结构如下(每部分用-分开):
* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
* 加起来刚好64位,为一个Long型。
* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
*/
public class SnowflakeIdUtils {
//开始时间截
private static final long twepoch = 1420041600000L;
//机器id所占的位数
private static final long workerIdBits = 5L;
//数据标识id所占的位数
private static final long datacenterIdBits = 5L;
//支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
private static final long maxWorkerId = -1L ^ (-1L << workerIdBits);
//支持的最大数据标识id,结果是31
private static final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
//序列在id中占的位数
private static final long sequenceBits = 12L;
//机器ID向左移12位
private static final long workerIdShift = sequenceBits;
//数据标识id向左移17位(12+5)
private static final long datacenterIdShift = sequenceBits + workerIdBits;
//时间截向左移22位(5+5+12)
private static final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
//生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
private static final long sequenceMask = -1L ^ (-1L << sequenceBits);
//工作机器ID(0~31)
private long workerId;
//数据中心ID(0~31)
private long datacenterId;
//毫秒内序列(0~4095)
private long sequence = 0L;
//上次生成ID的时间截
private long lastTimestamp = -1L;
// 默认实现,工作ID用本机ip生成,数据中心ID 用随机数
private static SnowflakeIdUtils snowflakeIdUtils;
/**
* 构造函数
*
* @param workerId 工作ID (0~31)
* @param datacenterId 数据中心ID (0~31)
*/
public SnowflakeIdUtils(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获得下一个ID (该方法是线程安全的)
*
* @return SnowflakeId
*/
public synchronized long nextId() {
long timestamp = timeGen();
//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
if (timestamp < lastTimestamp) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
//如果是同一时间生成的,则进行毫秒内序列
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
//毫秒内序列溢出
if (sequence == 0) {
//阻塞到下一个毫秒,获得新的时间戳
timestamp = tilNextMillis(lastTimestamp);
}
}
//时间戳改变,毫秒内序列重置
else {
sequence = 0L;
}
//上次生成ID的时间截
lastTimestamp = timestamp;
//移位并通过或运算拼到一起组成64位的ID
return ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift)
| sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*
* @param lastTimestamp 上次生成ID的时间截
* @return 当前时间戳
*/
protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
timestamp = timeGen();
}
return timestamp;
}
/**
* 返回以毫秒为单位的当前时间
*
* @return 当前时间(毫秒)
*/
protected long timeGen() {
return System.currentTimeMillis();
}
/**
* 获取本机ip
* @return
*/
private static String getHostIp(){
try{
Enumeration<NetworkInterface> allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()){
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration<InetAddress> addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()){
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null
&& ip instanceof Inet4Address
&& !ip.isLoopbackAddress() //loopback地址即本机地址,IPv4的loopback范围是127.0.0.0 ~ 127.255.255.255
&& ip.getHostAddress().indexOf(":")==-1){
System.out.println("本机的IP = " + ip.getHostAddress());
return ip.getHostAddress();
}
}
}
}catch(Exception e){
e.printStackTrace();
}
return null;
}
/*默认实现 逻辑:
工作id -> 用服务器ip生成,ip转化为long再模32(如有集群部署,且机器ip是连续的,
如我的集群服务器是192.168.180.54~58共五台机器,这样取模就能避免重复.如服务器ip不连续,慎用,慎用,慎用!!!!!! 重要事情说三遍)
数据中心ID -> 取0~31的随机数*/
static {
Random rd = new Random();
long workerId = rd.nextInt(31), datacenterId = rd.nextInt(31); //工作id,数据中心ID
//获取当前ip,生成工作id
String ip = getHostIp();
if(ip != null) {
workerId = Long.parseLong(ip.replaceAll("\\.", ""));
workerId = workerId % 32; //因为占用5位,模32
}
snowflakeIdUtils = new SnowflakeIdUtils(workerId, datacenterId);
}
//默认实现
public static long newId() {
return snowflakeIdUtils.nextId();
}
}
server.port=7001
#设置清理的间隔时间,而后这个时间使用的是毫秒单位(默认是60秒)
#eureka.server.eviction-interval-timer-in-ms=1000
#设置为false表示关闭保护模式
eureka.server.enable-self-preservation=true
#是否从Eureka获取注册信息
eureka.client.fetch-registry=false
#是否向注册中心注册自己
eureka.client.register-with-eureka=false
eureka.client.service-url.defaultZone=http://admin:admin@localhost:7001/eureka
eureka.instance.hostname=eureka-1
#访问eureka页面用户名密码
spring.security.user.name=admin
spring.security.user.password=admin
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
@Configuration
@EnableWebSecurity
public class EurekaSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
super.configure(http);
}
}
#服务暴露顿口号
server.port=9000
#服务名称
spring.application.name=springcloud-getaway
#注册到eureka
eureka.client.register-with-eureka=true
#eureka地址
eureka.client.service-url.defaultZone=http://admin:admin@localhost:7001/eureka
#配置服务实例
eureka.instance.instance-id=springcloud-getaway
#显示IP地址
eureka.instance.prefer-ip-address=true
#设置心跳的时间间隔(默认是30秒)
eureka.instance.lease-renewal-interval-in-seconds=2
#如果现在超过了5秒的间隔(默认是90秒)
eureka.instance.lease-expiration-duration-in-seconds=5
#springcloud-user
spring.cloud.gateway.routes[0].id=springcloud-user
spring.cloud.gateway.routes[0].uri=lb://springcloud-user
spring.cloud.gateway.routes[0].predicates[0]=Path=/user/**
# 降级配置
spring.cloud.gateway.routes[0].filters[0].name=Hystrix
spring.cloud.gateway.routes[0].filters[0].args.name=default
spring.cloud.gateway.routes[0].filters[0].args.fallbackUri=forward:/fallback
#springcloud-order
spring.cloud.gateway.routes[1].id=springcloud-order
spring.cloud.gateway.routes[1].uri=lb://springcloud-order
spring.cloud.gateway.routes[1].predicates[0]=Path=/order/**
# 降级配置
spring.cloud.gateway.routes[1].filters[0].name=Hystrix
spring.cloud.gateway.routes[1].filters[0].args.name=default
spring.cloud.gateway.routes[1].filters[0].args.fallbackUri=forward:/fallback
#启用基于服务发现的路由定位
spring.cloud.gateway.discovery.locator.enabled=true
#启用服务实例id名称小写支持
spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
#设置默认超时时间
#hystrix.metrics.polling-interval-ms=60000
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
@RestController
public class FallbackController {
@RequestMapping("/fallback")
@ResponseBody
public BaseResponse<Object> fallback() {
return BaseResponse.<Object>builder().code(90001).msg("服务暂不可用,请稍后重试!").version("getaway-1.0").result(null).build();
}
}
@SpringBootApplication
@EnableHystrix
public class GetawayApplication {
public static void main(String[] args) {
SpringApplication.run(GetawayApplication.class, args);
}
}
@Configuration
@EnableResourceServer//开启资源服务中心
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String secret;
@Value("${security.oauth2.authorization.check-token-access}")
private String checkTokenEndpointUrl;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore (){
return new RedisTokenStore(redisConnectionFactory);
}
@Override
public void configure(HttpSecurity http) throws Exception{
// 对/order 请求进行拦截 验证accessToken
http.authorizeRequests().antMatchers("/order/**").authenticated()
//scope需要包含order权限才可以访问
.antMatchers("/order/**").access("#oauth2.hasScope('" + "order" + "')")
//关闭csrf
.and().csrf().disable()
//设置session状态,为无状态
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setClientId(clientId);
tokenService.setClientSecret(secret);
tokenService.setCheckTokenEndpointUrl(checkTokenEndpointUrl);
return tokenService;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
//设置资源id配置数据库resourceIds进行资源权限访问,目前使用scope鉴权
// resources.resourceId("order");
resources.tokenServices(tokenService());
// 当拒绝访问时返回消息
resources.accessDeniedHandler((request, response, e) -> {
BaseResponse<Object> result = BaseResponse.BuildError(ApiError.ACCESS_DENIED, Constants.USER_VERSION);
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter()
.write(Objects.requireNonNull(JsonUtils.toJSON(result)));
});
// 当认证失败时返回消息
resources.authenticationEntryPoint((request, response, e) -> {
BaseResponse<Object> result = BaseResponse.BuildError(ApiError.TOKEN_ERROR, Constants.USER_VERSION);
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
response.getWriter()
.write(Objects.requireNonNull(JsonUtils.toJSON(result)));
});
}
}
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
OrderService orderService;
@PostMapping(value = "/login")
@ResponseBody
public BaseResponse<String> login(BaseRequest<Object> request){
return orderService.order(request);
}
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
OrderMapper orderMapper;
@Autowired
UserClientService userClientService;
@Override
public BaseResponse<String> order(BaseRequest<Object> request) {
return userClientService.login(BaseRequest.<Object>builder().build());
}
}
@FeignClient(name = "DUOERMEI-PROVIDER-USER", contextId = "user",configuration = FeignClientConfig.class,fallbackFactory = UserClientServiceFallbackFactory.class)
public interface UserClientService {
@RequestMapping(value = "/user/login")
@ResponseBody
BaseResponse<String> login(BaseRequest<Object> request);
}
//熔断
@Component
public class UserClientServiceFallbackFactory implements FallbackFactory<UserClientService> {
@Override
public UserClientService create(Throwable throwable) {
return new UserClientService() {
@Override
public BaseResponse<String> login(BaseRequest<Object> request) {
return BaseResponse.<String>builder().msg("服务暂不可用").build();
}
};
}
}
@SpringBootApplication(scanBasePackages = "com.springcloud")
@EnableEurekaClient
@EnableFeignClients("com.springcloud.orderservice.service")
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
#端口号
server.port=8084
spring.application.name=springcloud-order
#数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.username=root
spring.datasource.druid.password=root
#阿里druid连接池驱动配置信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
##### 连接池配置 #######
#初始大小
spring.datasource.druid.initial-size=5
#最大连接数
spring.datasource.druid.max-active=20
#链接等待超时时间
spring.datasource.druid.max-wait=60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
#配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000
#校验SQL
spring.datasource.druid.validation-query=SELECT 1
#验证连接的有效性
spring.datasource.druid.test-while-idle=true
#在连接归还到连接池时是否测试该连接
spring.datasource.druid.test-on-return=false
#获取连接时候验证,会影响性能
spring.datasource.druid.test-on-borrow=false
#打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
##### WebStatFilter配置 #######
#启用StatFilter
spring.datasource.druid.web-stat-filter.enabled=true
#添加过滤规则
spring.datasource.druid.web-stat-filter.url-pattern=/*
#排除一些不必要的url
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
#开启session统计功能
spring.datasource.druid.web-stat-filter.session-stat-enable=true
#缺省sessionStatMaxCount是1000个
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
#身份标识 从session中获取
#spring.datasource.druid.web-stat-filter.principal-session-name=
#user信息保存在cookie中
#spring.datasource.druid.web-stat-filter.principal-cookie-name=
#配置profileEnable能够监控单个url调用的sql列表
#spring.datasource.druid.web-stat-filter.profile-enable=
##### StatViewServlet配置 #######
#启用内置的监控页面
spring.datasource.druid.stat-view-servlet.enabled=true
#内置监控页面的地址
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#关闭 Reset All 功能
spring.datasource.druid.stat-view-servlet.reset-enable=false
#设置登录用户名
spring.datasource.druid.stat-view-servlet.login-username=admin
#设置登录密码
spring.datasource.druid.stat-view-servlet.login-password=admin
#白名单(如果allow没有配置或者为空,则允许所有访问)
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
#黑名单(deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝)
spring.datasource.druid.stat-view-servlet.deny=
#Redis配置
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=
spring.redis.jedis.pool.max-active=200
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=1200ms
#配置mapper路径,开启驼峰转换
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
#management.server.port=8088
#management.health.db.enabled=true
#management.health.mail.enabled=true
#management.endpoint.env.enabled=true
#management.endpoints.web.exposure.include=*
info.app.name=springcloud-order
info.company.name=springcloud
info.build.artifactId=$project.artifactId$
info.build.modelVersion=$project.modelVersion$
#注册到eureka
eureka.client.register-with-eureka=true
#eureka地址
eureka.client.service-url.defaultZone=http://admin:admin@localhost:7001/eureka
#配置服务实例
eureka.instance.instance-id=springcloud-order
#显示IP地址
eureka.instance.prefer-ip-address=true
#设置心跳的时间间隔(默认是30秒)
eureka.instance.lease-renewal-interval-in-seconds=2
#如果现在超过了5秒的间隔(默认是90秒)
eureka.instance.lease-expiration-duration-in-seconds=5
security.oauth2.client.client-id=springcloud-app
security.oauth2.client.client-secret=springcloud-app-secret-4f3214bd-ead5-4978-93c9-2d8f9a42b82d
security.oauth2.client.user-authorization-uri=http://localhost:9999/oauth/authorize
security.oauth2.client.access-token-uri=http://localhost:9999/oauth/token
#security.oauth2.resource.jwt.key-uri=http://localhost:9999/oauth/token_key
#security.oauth2.resource.jwt.key-value=springcloud
security.oauth2.resource.id=order-client
security.oauth2.resource.user-info-uri=user-info
security.oauth2.authorization.check-token-access=http://localhost:9999/oauth/check_token
@RestController
@RequestMapping(value = "/user")
public class UserController {
@Autowired
UserService userService;
@PostMapping(value = "/login")
@ResponseBody
public BaseResponse<String> login(BaseRequest<Object> request){
return userService.login(request);
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper userMapper;
@Override
public BaseResponse<String> login(BaseRequest<Object> request) {
return BaseResponse.success(UUID.randomUUID().toString(),"user-1.0.0");
}
}
@SpringBootApplication(scanBasePackages = "com.springcloud")
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
#端口号
server.port=8088
spring.application.name=springcloud-user
#数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?characterEncoding=utf8&allowMultiQueries=true&useSSL=true&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.druid.username=root
spring.datasource.druid.password=root
#阿里druid连接池驱动配置信息
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
##### 连接池配置 #######
#初始大小
spring.datasource.druid.initial-size=5
#最大连接数
spring.datasource.druid.max-active=20
#链接等待超时时间
spring.datasource.druid.max-wait=60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
#配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000
#校验SQL
spring.datasource.druid.validation-query=SELECT 1
#验证连接的有效性
spring.datasource.druid.test-while-idle=true
#在连接归还到连接池时是否测试该连接
spring.datasource.druid.test-on-return=false
#获取连接时候验证,会影响性能
spring.datasource.druid.test-on-borrow=false
#打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.druid.pool-prepared-statements=true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
#配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
spring.datasource.druid.filters=stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
##### WebStatFilter配置 #######
#启用StatFilter
spring.datasource.druid.web-stat-filter.enabled=true
#添加过滤规则
spring.datasource.druid.web-stat-filter.url-pattern=/*
#排除一些不必要的url
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
#开启session统计功能
spring.datasource.druid.web-stat-filter.session-stat-enable=true
#缺省sessionStatMaxCount是1000个
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
#身份标识 从session中获取
#spring.datasource.druid.web-stat-filter.principal-session-name=
#user信息保存在cookie中
#spring.datasource.druid.web-stat-filter.principal-cookie-name=
#配置profileEnable能够监控单个url调用的sql列表
#spring.datasource.druid.web-stat-filter.profile-enable=
##### StatViewServlet配置 #######
#启用内置的监控页面
spring.datasource.druid.stat-view-servlet.enabled=true
#内置监控页面的地址
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#关闭 Reset All 功能
spring.datasource.druid.stat-view-servlet.reset-enable=false
#设置登录用户名
spring.datasource.druid.stat-view-servlet.login-username=admin
#设置登录密码
spring.datasource.druid.stat-view-servlet.login-password=admin
#白名单(如果allow没有配置或者为空,则允许所有访问)
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
#黑名单(deny优先于allow,如果在deny列表中,就算在allow列表中,也会被拒绝)
spring.datasource.druid.stat-view-servlet.deny=
#Redis配置
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=
spring.redis.jedis.pool.max-active=200
spring.redis.jedis.pool.max-wait=-1
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=1200ms
#配置mapper路径,开启驼峰转换
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
#management.server.port=8088
#management.health.db.enabled=true
#management.health.mail.enabled=true
#management.endpoint.env.enabled=true
#management.endpoints.web.exposure.include=*
info.app.name=springcloud-user
info.company.name=springcloud
info.build.artifactId=$project.artifactId$
info.build.modelVersion=$project.modelVersion$
#注册到eureka
eureka.client.register-with-eureka=true
#eureka地址
eureka.client.service-url.defaultZone=http://admin:admin@localhost:7001/eureka
#配置服务实例
eureka.instance.instance-id=springcloud-user
#显示IP地址
eureka.instance.prefer-ip-address=true
#设置心跳的时间间隔(默认是30秒)
eureka.instance.lease-renewal-interval-in-seconds=2
#如果现在超过了5秒的间隔(默认是90秒)
eureka.instance.lease-expiration-duration-in-seconds=5
security.oauth2.client.client-id=springcloud-app
security.oauth2.client.client-secret=springcloud-app-secret-4f3214bd-ead5-4978-93c9-2d8f9a42b82d
security.oauth2.client.user-authorization-uri=http://localhost:9999/oauth/authorize
security.oauth2.client.access-token-uri=http://localhost:9999/oauth/token
#security.oauth2.resource.jwt.key-uri=http://localhost:9999/oauth/token_key
#security.oauth2.resource.jwt.key-value=springcloud
security.oauth2.resource.id=user-client
security.oauth2.resource.user-info-uri=user-info
security.oauth2.authorization.check-token-access=http://localhost:9999/oauth/check_token
BCryptPasswordEncoder b = new BCryptPasswordEncoder();
System.out.println(b.encode("springcloud-app-secret-4f3214bd-ead5-4978-93c9-2d8f9a42b82d"));
再访问一下我们的/order/login接口,可以看到实际上通过fegin还是调用的/user/login接口
然后我们来试一下自定义的短信验证码登录,没有实现短信服务,就直接Redis设置一个验证码。
数据库当前也就只有一个用户。
然后我们使用136666666666来实现短信验证码登录,并且用户不存在就注册
然后使用当前token访问一下user服务,order服务
可以看到数据库的数据也进去了
像自定义TokenServices什么的,有些地方有什么更好的方法,有什么有问题的地方还希望不吝赐教。