关于理论,这里不做过多的介绍,如果想要更多的了解,可以参考下面这个地址
http://ifeve.com/oauth2-tutorial-all/
俗话说,实践出真知,理论讲再多都是扯蛋。
通过了解,我们知道,在auth2.0的世界里,有针对几种不同的情况有不同的实现方式,分别是客户端、用户名及密码、授权码、隐式授权等。我这里目前对隐式授权的方式没有深入的研究,所以不对这种情况做进一步的说明
我这里工程是以Springboot为框架,如果是使用SpringCloud微服务架构的朋友,使用的方式也是一模一样的。
下面来看分模块
(1)针对客户模式的相关配置
AuthorizationServerConfig类的内容如下:
package com.hl.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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.TokenStore;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private RedisConnectionFactory connectionFactory;
@Autowired
private DataSource dataSource;
@Bean
public TokenStore tokenStore() {
return new MyRedisTokenStore(connectionFactory);
}
/*
* AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
* */
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore()).
allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/*
* AuthorizationServerSecurityConfigurer 用来配置令牌端点(Token Endpoint)的安全约束
* */
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许表单认证
security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
/*
ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息
* 1.授权码模式(authorization code)
2.简化模式(implicit)
3.密码模式(resource owner password credentials)
4.客户端模式(client credentials)
* */
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
String finalSecret = "{bcrypt}" + new BCryptPasswordEncoder().encode("123456");
clients.
// jdbc(dataSource).
inMemory().
// ================这一段是以客户端的形式获得token===============
withClient("web-auth")
.resourceIds("web-auth")
.authorizedGrantTypes("client_credentials", "refresh_token")
.scopes("all","read", "write","xxxx") // 这个scope可以自己,请求的时候,需要匹配即可
.authorities("client_credentials")
.secret(finalSecret)
.accessTokenValiditySeconds(1200)
.refreshTokenValiditySeconds(50000);
// .and()
//
// // ================这一段是以密码的形式获得token===============
// .withClient("by_pwd")
// .resourceIds("by_pwd")
// .authorizedGrantTypes("password", "refresh_token")
// .scopes("server")
// .authorities("password")
// .secret(finalSecret)
// .accessTokenValiditySeconds(1200)
// .refreshTokenValiditySeconds(50000);
}
}
MyRedisTokenStore类的内容如下:
package com.hl.config;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.JdkSerializationStrategy;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStoreSerializationStrategy;
import java.util.*;
public class MyRedisTokenStore implements TokenStore {
private static final String ACCESS = "access:";
private static final String AUTH_TO_ACCESS = "auth_to_access:";
private static final String AUTH = "auth:";
private static final String REFRESH_AUTH = "refresh_auth:";
private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
private static final String REFRESH = "refresh:";
private static final String REFRESH_TO_ACCESS = "refresh_to_access:";
private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";
private static final String UNAME_TO_ACCESS = "uname_to_access:";
private final RedisConnectionFactory connectionFactory;
private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
private String prefix = "";
public MyRedisTokenStore(RedisConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
this.authenticationKeyGenerator = authenticationKeyGenerator;
}
public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) {
this.serializationStrategy = serializationStrategy;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
private RedisConnection getConnection() {
return connectionFactory.getConnection();
}
private byte[] serialize(Object object) {
return serializationStrategy.serialize(object);
}
private byte[] serializeKey(String object) {
return serialize(prefix + object);
}
private OAuth2AccessToken deserializeAccessToken(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class);
}
private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2Authentication.class);
}
private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class);
}
private byte[] serialize(String string) {
return serializationStrategy.serialize(string);
}
private String deserializeString(byte[] bytes) {
return serializationStrategy.deserializeString(bytes);
}
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
String key = authenticationKeyGenerator.extractKey(authentication);
byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = conn.get(serializedKey);
} finally {
conn.close();
}
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
if (accessToken != null) {
OAuth2Authentication storedAuthentication = readAuthentication(accessToken.getValue());
if ((storedAuthentication == null || !key.equals(authenticationKeyGenerator.extractKey(storedAuthentication)))) {
// Keep the stores consistent (maybe the same user is
// represented by this authentication but the details have
// changed)
storeAccessToken(accessToken, authentication);
}
}
return accessToken;
}
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
return readAuthentication(token.getValue());
}
@Override
public OAuth2Authentication readAuthentication(String token) {
byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = conn.get(serializeKey(AUTH + token));
} finally {
conn.close();
}
OAuth2Authentication auth = deserializeAuthentication(bytes);
return auth;
}
@Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
return readAuthenticationForRefreshToken(token.getValue());
}
public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
RedisConnection conn = getConnection();
try {
byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));
OAuth2Authentication auth = deserializeAuthentication(bytes);
return auth;
} finally {
conn.close();
}
}
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication);
byte[] accessKey = serializeKey(ACCESS + token.getValue());
byte[] authKey = serializeKey(AUTH + token.getValue());
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));
byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.stringCommands().set(accessKey, serializedAccessToken);
conn.set(authKey, serializedAuth);
conn.set(authToAccessKey, serializedAccessToken);
if (!authentication.isClientOnly()) {
conn.rPush(approvalKey, serializedAccessToken);
}
conn.rPush(clientId, serializedAccessToken);
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
conn.expire(accessKey, seconds);
conn.expire(authKey, seconds);
conn.expire(authToAccessKey, seconds);
conn.expire(clientId, seconds);
conn.expire(approvalKey, seconds);
}
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null && refreshToken.getValue() != null) {
byte[] refresh = serialize(token.getRefreshToken().getValue());
byte[] auth = serialize(token.getValue());
byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());
conn.set(refreshToAccessKey, auth);
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue());
conn.set(accessToRefreshKey, refresh);
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;
Date expiration = expiringRefreshToken.getExpiration();
if (expiration != null) {
int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
.intValue();
conn.expire(refreshToAccessKey, seconds);
conn.expire(accessToRefreshKey, seconds);
}
}
}
conn.closePipeline();
} finally {
conn.close();
}
}
private static String getApprovalKey(OAuth2Authentication authentication) {
String userName = authentication.getUserAuthentication() == null ? ""
: authentication.getUserAuthentication().getName();
return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
}
private static String getApprovalKey(String clientId, String userName) {
return clientId + (userName == null ? "" : ":" + userName);
}
@Override
public void removeAccessToken(OAuth2AccessToken accessToken) {
removeAccessToken(accessToken.getValue());
}
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
byte[] key = serializeKey(ACCESS + tokenValue);
byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = conn.get(key);
} finally {
conn.close();
}
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
return accessToken;
}
public void removeAccessToken(String tokenValue) {
byte[] accessKey = serializeKey(ACCESS + tokenValue);
byte[] authKey = serializeKey(AUTH + tokenValue);
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.get(accessKey);
conn.get(authKey);
conn.del(accessKey);
conn.del(accessToRefreshKey);
// Don't remove the refresh token - it's up to the caller to do that
conn.del(authKey);
List
SecurityConfiguration类的内容如下:
package com.hl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
// 指定以下面的路径前缀的需要作校验
.antMatchers("/oauth/**","/actuator/**").permitAll()
.and()
.httpBasic().disable();
}
}
application.properties配置文件的内容如下:
spring.application.name=auth-center
server.port=3040
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=你的ip
# Redis服务器连接端口
spring.redis.port=你的端口
# Redis服务器连接密码(默认为空)
spring.redis.password=你的登录密码
logging.level.org.springframework.security=debug
(2)针对密码和授权码模式的相关的配置
AuthorizationServerConfiguration类的内容如下:
package com.hl.config;
import com.hl.service.UserServiceDetail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import javax.sql.DataSource;
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private TokenStore tokenStore;
@Autowired
private UserServiceDetail userServiceDetail;
@Autowired
private ClientDetailsService clientDetailsService;
static final Logger logger = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
@Bean // 声明 ClientDetails实现
public ClientDetailsService clientDetailsService() {
return new JdbcClientDetailsService(dataSource);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// redisTokenStore
// endpoints.tokenStore(new MyRedisTokenStore(redisConnectionFactory))
// .authenticationManager(authenticationManager)
// .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// 存数据库
endpoints.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userServiceDetail);
// 配置tokenServices参数
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(endpoints.getTokenStore());
//支持refreshtoken
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenServices.setAccessTokenValiditySeconds(60 * 5);
//重复使用
tokenServices.setReuseRefreshToken(false);
tokenServices.setRefreshTokenValiditySeconds(60 * 10);
endpoints.tokenServices(tokenServices);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
// 允许表单认证
security.allowFormAuthenticationForClients()
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
}
SecurityConfiguration类的内容如下
package com.hl.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers().anyRequest()
.and()
.authorizeRequests()
// .antMatchers("/oauth/**").permitAll();
.antMatchers("/").authenticated();
/* http.csrf().disable().exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests().
antMatchers("/favicon.ico").permitAll()
.antMatchers("/oauth/**").permitAll()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.and()
.httpBasic().disable();*/
http.authorizeRequests().antMatchers("/**").fullyAuthenticated().and().httpBasic(); //拦截所有请求 通过httpBasic进行认证
}
}
UserServiceDetail类的内容如下:
package com.hl.service;
import com.hl.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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.Service;
@Service
public class UserServiceDetail implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username);
}
}
applicationContext.properties配置文件的内容如下:
spring.application.name=Auth-Center
server.port=3040
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/auth2.0-db?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
logging.level.org.springframework.security=debug
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
security.basic.enabled=true
spring.security.user.name=admin
spring.security.user.password=admin
spring.main.allow-bean-definition-overriding=true
(3) 消费方配置如下
OAuth2ClientConfig类的内容如下
package com.hl.oauth2;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
/*
鉴权过滤器
* @ OAuth2AuthenticationProcessingFilter
*
* */
@EnableOAuth2Client
@EnableConfigurationProperties
@Configuration
public class OAuth2ClientConfig {
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
// @Bean
// public RequestInterceptor oauth2FeignRequestInterceptor(ClientCredentialsResourceDetails clientCredentialsResourceDetails) {
// return new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), clientCredentialsResourceDetails);
// }
@Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
RefreshTokenAuthenticationEntryPoint类的内容如下
package com.hl.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
public class RefreshTokenAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
@Autowired
private ClientCredentialsResourceDetails clientCredentialsResourceDetails;
private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();
@Autowired
RestTemplate restTemplate;
private static String oauth_server_url = "http://localhost:3040/oauth/token";
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
try {
//解析异常,如果是401则处理
ResponseEntity result = exceptionTranslator.translate(authException);
if (result.getStatusCode() == HttpStatus.UNAUTHORIZED) {
MultiValueMap formData = new LinkedMultiValueMap();
formData.add("client_id", clientCredentialsResourceDetails.getClientId());
formData.add("client_secret", clientCredentialsResourceDetails.getClientSecret());
formData.add("grant_type", clientCredentialsResourceDetails.getGrantType());
formData.add("scope", String.join(",",clientCredentialsResourceDetails.getScope()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
Map map = restTemplate.exchange(oauth_server_url, HttpMethod.POST,
new HttpEntity>(formData, headers), Map.class).getBody();
//如果刷新异常
if (map.get("error") != null) {
// 返回指定格式的错误信息
response.setStatus(401);
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.getWriter().print("{\"code\":1,\"message\":\"" + map.get("error_description") + "\"}");
response.getWriter().flush();
//如果是网页,跳转到登陆页面
//response.sendRedirect("login");
} else {
//如果刷新成功则存储cookie并且跳转到原来需要访问的页面
for (Object key : map.keySet()) {
response.addCookie(new Cookie(key.toString(), map.get(key).toString()));
}
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
// response.sendRedirect(request.getRequestURI());
//将access_token保存
}
} else {
//如果不是401异常,则以默认的方法继续处理其他异常
super.commence(request, response, authException);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ResourceServerConfiguration的内容如下
package com.hl.oauth2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
@Configuration
@EnableResourceServer
//启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// @Autowired
// private TokenStore tokenStore;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// 配置order访问控制,必须认证后才可以访问
http.authorizeRequests()
.antMatchers("/order/**","/user/**").authenticated();
}
/*
* 把token验证失败后,重新刷新token的类设置到 OAuth2AuthenticationProcessingFilter
* token验证过滤器中
* */
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
// 此处通过这一句,可以根据不同的异步作不同的处理。此处是在有异常时刷新token
resources.authenticationEntryPoint(new RefreshTokenAuthenticationEntryPoint()); // 这个地方放进去的切入点,是在OAuth2AuthenticationProcessingFilter中的doFilter异常处理处起作用的
// resources.tokenStore(tokenStore);
}
}
applicationContext.properties配置文件的内容如下
spring.application.name=web-auth
server.port=8083
#########################管理监控的一些监控端点###################
management.endpoint.health.show-details=always
management.endpoint.shutdown.enabled=true
#hystrix.stream 开放所有的监控接口
management.endpoints.web.exposure.include=*
################### 其它的一些相关的配置##############
spring.main.allow-bean-definition-overriding=true
###########下面这几个属性是以客户模式配置Auth获取请求接口时token
##其中clientId、secret、grant-type是在《micro-security》中的AuthorizationServerConfig.java中配置的
# 此uri借助了zuul的路由,它是由auth2自动调用的。注意,此处只能使用ip:port的方式,因为是由auth框架自己调用,并没有像ribbon那样有服务的列表
security.oauth2.resource.user-info-uri=http://localhost:3040/security/check
security.oauth2.client.access-token-uri=http://localhost:3040/oauth/token
###以下部分是以客户端方式的权限校验配置的参数
security.oauth2.resource.prefer-token-info=false
security.oauth2.client.clientId=${spring.application.name}
security.oauth2.client.client-secret=123456
security.oauth2.client.grant-type=client_credentials
security.oauth2.client.scope=all
(4) 服务方配置如下
OAuth2ClientConfig类的内容如下
package com.hl.oauth2;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
@EnableOAuth2Client
@EnableConfigurationProperties
@Configuration
public class OAuth2ClientConfig {
@Bean
@ConfigurationProperties(prefix = "security.oauth2.client")
public ClientCredentialsResourceDetails clientCredentialsResourceDetails() {
return new ClientCredentialsResourceDetails();
}
@Bean
public OAuth2RestTemplate clientCredentialsRestTemplate() {
return new OAuth2RestTemplate(clientCredentialsResourceDetails());
}
}
RefreshTokenAuthenticationEntryPoint类的内容如下
package com.hl.oauth2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
public class RefreshTokenAuthenticationEntryPoint extends OAuth2AuthenticationEntryPoint {
@Autowired
private ClientCredentialsResourceDetails clientCredentialsResourceDetails;
private WebResponseExceptionTranslator exceptionTranslator = new DefaultWebResponseExceptionTranslator();
@Autowired
RestTemplate restTemplate;
private static String oauth_server_url = "http://localhos:3040/oauth/token";
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
try {
//解析异常,如果是401则处理
ResponseEntity result = exceptionTranslator.translate(authException);
if (result.getStatusCode() == HttpStatus.UNAUTHORIZED) {
MultiValueMap formData = new LinkedMultiValueMap();
formData.add("client_id", clientCredentialsResourceDetails.getClientId());
formData.add("client_secret", clientCredentialsResourceDetails.getClientSecret());
formData.add("grant_type", clientCredentialsResourceDetails.getGrantType());
formData.add("scope", String.join(",",clientCredentialsResourceDetails.getScope()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
Map map = restTemplate.exchange(oauth_server_url, HttpMethod.POST,
new HttpEntity>(formData, headers), Map.class).getBody();
//如果刷新异常
if (map.get("error") != null) {
// 返回指定格式的错误信息
response.setStatus(401);
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.getWriter().print("{\"code\":1,\"message\":\"" + map.get("error_description") + "\"}");
response.getWriter().flush();
//如果是网页,跳转到登陆页面
//response.sendRedirect("login");
} else {
//如果刷新成功则存储cookie并且跳转到原来需要访问的页面
for (Object key : map.keySet()) {
response.addCookie(new Cookie(key.toString(), map.get(key).toString()));
}
request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
// response.sendRedirect(request.getRequestURI());
//将access_token保存
}
} else {
//如果不是401异常,则以默认的方法继续处理其他异常
super.commence(request, response, authException);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
ResourceServerConfiguration类的内容如下
package com.hl.oauth2;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
@Configuration
@EnableResourceServer
//启用全局方法安全注解,就可以在方法上使用注解来对请求进行过滤
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
// @Autowired
// private TokenStore tokenStore;
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
// 配置路径中只要是以order或user开头的,都需要进行访问的控制,必须认证后才可以访问
http.authorizeRequests()
.antMatchers("/order/**","/user/**").authenticated();
}
/*
* 把token验证失败后,重新刷新token的类设置到 OAuth2AuthenticationProcessingFilter
* token验证过滤器中
* */
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
super.configure(resources);
// resources.authenticationEntryPoint(new RefreshTokenAuthenticationEntryPoint());
// resources.tokenStore(tokenStore);
}
}
applicationContext.properties配置文件的内容如下
spring.application.name=order-auth
server.port=8081
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/consult?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
#sql日志,会输出sql语句
logging.level.com.hl.dao=debug
#####################actuator/监控端点管理相关的配置#############
management.endpoint.health.show-details=always
management.endpoint.shutdown.enabled=true
#hystrix.stream 开放所有的监控接口
management.endpoints.web.exposure.include=*
################### 其它的一些相关的配置##############
spring.main.allow-bean-definition-overriding=true
###########下面这几个属性是以客户模式配置Auth获取请求接口时token
##其中clientId、secret、grant-type是在《micro-security》中的AuthorizationServerConfig.java中配置的
# 此uri借助了zuul的路由,它是由auth2自动调用的。注意,此处只能使用ip:port的方式,因为是由auth框架自己调用,并没有像ribbon那样有服务的列表
security.oauth2.resource.user-info-uri=http://localhost:3040/security/check
security.oauth2.client.access-token-uri=http://localhost:3040/oauth/token
###以下部分是客户端方式的权限校验
security.oauth2.resource.prefer-token-info=false
security.oauth2.client.clientId=${spring.application.name}
security.oauth2.client.client-secret=123456
security.oauth2.client.grant-type=client_credentials
security.oauth2.client.scope=all
(5)数据库的结构如下
1、先看客户端的模式
分别启动《Auth-Center-Only-For-Client-Type》、《Business-Logic》和《Business-Order》三个工程,然后,调用效果如下
需要注意的是,在使用客户端模式的时候,请求参数必须要和配置类中定义的相同才可以。
2、看授权码和密码模式
分别启动《Auth-Center》、《Business-Logic》和《Business-Order》三个工程,然后,调用效果如下
(2)授权码模式
效果如下
需要说明的是,在使用授权码的模式的时候,如果随意使用auth2和springboot的一些依赖,会出现”At least one redirect_uri must be registered with the client“的错误。因此,对于这个Auth-Center这个工程的springboot依赖就按照pom.xml文件里面配置就行。
demo代码地址:https://gitee.com/im_a_follower_of_code_cloud/Auth20_Study.git