场景:
按职能,鉴权系统需要划分 网关(spring gateway) + 鉴权(auth-server)。本文通过实践搭建鉴权系统。
spring gateway
首先引入pom依赖
1、resilience 熔断器
2、gateway 网关
3、eureka client 服务注册中心
4、lombok插件
5、actuator状态监控
<dependencies>
<!-- 熔断器-->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-feign</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Gateway -->
<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>
<version>2.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
application.yml配置
1、gateway信息
2、actuator状态信息
3、配置eureka server地址及注册信息
4、日志配置
5、获取oauth2的jwt key
server:
port: 18890
spring:
application:
name: open-api-gateway
profiles:
active: local
cloud:
gateway:
discovery:
locator:
enabled: true
lower-case-service-id: true
globalcors:
corsConfigurations:
'[publicstaticoauth
private final AuthenticationManager authenticationManager;
private final DataSource dataSource;
private final OAuthUserService userService;
private final RedisConnectionFactory redisConnectionFactory;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients()
.checkTokenAccess("permitAll()")
.tokenKeyAccess("permitAll()")
.authenticationEntryPoint(authenticationEntryPoint())
.accessDeniedHandler(accessDeniedHandler());
}
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return (request, response, accessDeniedException) -> {
Response result = new Response(1, accessDeniedException.getMessage());
writerResponse(response, result, HttpStatus.FORBIDDEN.value());
};
}
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> {
Response result = new Response(1, authException.getMessage());
writerResponse(response, result, HttpStatus.UNAUTHORIZED.value());
};
}
private void writerResponse(HttpServletResponse response, Response result, int status) throws IOException {
response.setStatus(status);
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setCharacterEncoding("UTF-8");
response.getWriter().print(result.getErrMsg());
response.getWriter().flush();
}
@Bean("redisTokenStore")
public TokenStore redisTokenStore() {
return new CustomRedisTokenStore(redisConnectionFactory);
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory().getKeyPair(jwtProperties.getAuthJwtKey()));
return jwtAccessTokenConverter;
}
@Bean
public KeyStoreKeyFactory keyStoreKeyFactory() {
return new KeyStoreKeyFactory(new ClassPathResource(jwtProperties.getAuthJwtJks()), jwtProperties.getAuthJwtPassword().toCharArray());
}
@Bean
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(redisTokenStore());
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setReuseRefreshToken(false);
defaultTokenServices.setAccessTokenValiditySeconds(jwtProperties.getAccessTokenValiditySeconds());
defaultTokenServices.setRefreshTokenValiditySeconds(jwtProperties.getRefreshTokenValiditySeconds());
return defaultTokenServices;
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//开启密码授权类型
endpoints
.authenticationManager(authenticationManager)
//配置token存储方式
.tokenStore(redisTokenStore())
//需要额外配置,用于refres_token
.userDetailsService(userService)
//
.tokenServices(tokenServices())
.accessTokenConverter(jwtAccessTokenConverter())
.exceptionTranslator(exceptionTranslator());
}
@Bean
public WebResponseExceptionTranslator exceptionTranslator() {
return exception -> {
return ResponseEntity.status(HttpStatus.OK).body(new OAuth2Exception(exception.getMessage()));
};
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// clients.withClientDetails(clientDetails());
clients.inMemory()
.withClient("open_api")
.authorizedGrantTypes("password","refresh_token")
.authorities("USER")
.scopes("read", "write")
.resourceIds("auth-server")
.secret(new BCryptPasswordEncoder().encode("digquant"));
}
}
2、ResourceServerConfig 资源服务配置
package com.digquant.config;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
@Order(6)
@Configuration
@AllArgsConstructor
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private final AccessDeniedHandler accessDeniedHandler;
private final AuthenticationEntryPoint authenticationEntryPoint;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
.and().authorizeRequests()
.antMatchers("/swagger-ui.html","/webjars/**").permitAll()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().permitAll();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
.resourceId("auth-server");
}
}
3、SecurityConfig配置
package com.digquant.config;
import com.digquant.service.CustomAuthenticationProvider;
import com.digquant.service.OAuthUserService;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Order(7)
@Configuration
@EnableWebSecurity
@AllArgsConstructor
@AutoConfigureAfter(ResourceServerConfig.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final OAuthUserService userService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/public/**").permitAll()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/private/**").permitAll()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/favor.ico");
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
CustomAuthenticationProvider provider = new CustomAuthenticationProvider()
.setUserDetailsService(userService)
.setPasswordEncoder(passwordEncoder());
provider.setHideUserNotFoundExceptions(false);
return provider;
}
}
4、JwkController 用于gateway 请求jwt私钥
package com.digquant.controller;
import com.digquant.enity.JWTProperties;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.web.bind.annotation.*;
import java.security.interfaces.RSAPublicKey;
import java.util.Map;
@Api(tags = "jwk")
@RestController
@RequestMapping("/private")
@AllArgsConstructor
public class JwkController {
private final KeyStoreKeyFactory keyStoreKeyFactory;
private final JWTProperties jwtProperties;
@ApiOperation("获取jwk")
@PostMapping("/jwk_public_key")
public Map<String, Object> getKey() {
RSAPublicKey publicKey = (RSAPublicKey) keyStoreKeyFactory.getKeyPair(jwtProperties.getAuthJwtKey()).getPublic();
RSAKey key = new RSAKey.Builder(publicKey).build();
return new JWKSet(key).toJSONObject();
}
}
注意私钥放到项目的resources目录下
5、用户鉴权服务,获取用户信息
package com.digquant.service;
import com.digquant.enity.to.AuthenticationTO;
import com.digquant.enums.LoginType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
@Slf4j
@Component
public class OAuthUserService implements UserDetailsService {
@Autowired(required = false)
private List<OAuthUserProcessor> oAuthUserProcessors;
public UserDetails loadUser(String username, UsernamePasswordAuthenticationToken authentication) {
AuthenticationTO authenticationTO = new AuthenticationTO();
authenticationTO.setUsername(username);
authenticationTO.setPassword((String) authentication.getCredentials());
Map map = (Map) authentication.getDetails();
String scope = (String) map.get("scope");
String grantType = (String) map.get("grant_type");
String clientId = (String) map.get("client_id");
authenticationTO.setScope(scope);
authenticationTO.setGrantType(grantType);
authenticationTO.setLoginType(LoginType.PASSWORD);
authenticationTO.setClientId(clientId);
if (log.isDebugEnabled()) {
log.debug("请求认证参数:{}", authenticationTO);
}
if (!CollectionUtils.isEmpty(oAuthUserProcessors)) {
//目前只支持客户端密码登录方式
for (OAuthUserProcessor oAuthUserProcessor : oAuthUserProcessors) {
if (oAuthUserProcessor.support(authenticationTO)) {
UserDetails userDetails = oAuthUserProcessor.findUser(authenticationTO);
//TODO 需要加载OpenApi用户的权限
loadAuthorities(userDetails, authenticationTO);
return userDetails;
}
}
}
throw new UsernameNotFoundException("用户不存在");
}
private void loadAuthorities(UserDetails userDetails, AuthenticationTO authenticationTO) {
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return null;
}
}
获取token
refresh_token
到此这篇关于Spring gateway + Oauth2实现单点登录的文章就介绍到这了,更多相关Spring gateway Oauth2单点登录内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!