本项目主要介绍用户的授权认证管理在分布式系统的应用,使用的是spring-cloud-starter-oauth2
主要实现以下功能:
1:通过用户名和密码进行鉴权,获取接口调用token
2:通过token进行资源服务器的访问
1:daisyday-manage-auth:oauth2.0鉴权认证服务
2:daisyday-manage-eureka:eureka注册服务,用于springcloud微服务注册
3:daisyday-manage-gateway:网关服务,使用的是Spring Cloud Gateway做路由、token认证、负载均衡
4:daisyday-service-order:资源服务,本文以订单系统为例
说明:oauth.sql 是鉴权和认证所需要的的表和数据
https://gitee.com/duyunming/daisyday-core-project.git
一、AuthorizationServer类继承AuthorizationServerConfigurerAdapter(认证服务配置适配器) 我们需要重写三个方法(不写就是用默认的, 根据需求,一般需要重写)
【1】:@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
访问安全配置。
}
【2】: @Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
决定clientDeatils信息的处理服务
}
【3】: @Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
访问端点配置。tokenStroe、tokenEnhancer服务
}
package com.daisyday.manage.auth.config;
import com.daisyday.manage.auth.enhancer.CustomTokenEnhancer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import javax.sql.DataSource;
import java.util.Arrays;
@Configuration
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
/**
* token生成策略
*/
@Autowired
private TokenStore tokenStore;
/**
* 授权码服务类
*/
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
/**
* 身份信息管理类
*/
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
/**
* token信息的额外信息处理
*/
@Autowired
private CustomTokenEnhancer customTokenEnhancer;
@Autowired
private DataSource dataSource;
/**
* clientDetail 信息里的client_secret字段加解密器
* client_secret密码加密器
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
// return NoOpPasswordEncoder.getInstance();
}
/**
* 配置客户端信息
* 配置从哪里获取ClientDetails信息
* 在client_credentials授权方式下,只要这个ClientDetails信息。
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
/**
* 一般生产环境,将客户端信息存储在内存中
*/
clients.jdbc(dataSource);
/**
* 测试用,将客户端信息存储在内存中
*/
/*clients.inMemory()
.withClient("c1") // client_id
.secret("secret") // client_secret
.authorizedGrantTypes("authorization_code") // 该client允许的授权类型
.scopes("app") // 允许的授权范围
.autoApprove(true); //登录后绕过批准询问(/oauth/confirm_access)*/
}
/**
* token增强类
*
* @return
*/
public TokenEnhancerChain tokenEnhancer() {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(customTokenEnhancer, jwtAccessTokenConverter));
return tokenEnhancerChain;
}
/**
* token 令牌服务
*
* @return
*/
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
services.setSupportRefreshToken(true); //支持refreshtoken
services.setTokenStore(tokenStore);//token的保存方式
services.setTokenEnhancer(tokenEnhancer());//token里加点信息
return services;
}
@Bean
public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
return new JdbcAuthorizationCodeServices(dataSource);
}
/**
* 配置认证服务端点
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.authorizationCodeServices(authorizationCodeServices)
.tokenServices(tokenServices())
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
/**
* 替换原有的默认授权地址为新的授权地址
*/
// endpoints.pathMapping("/oauth/token", "/oauth/login");
}
/**
* 配置:安全检查流程,用来配置令牌端点(Token Endpoint)的安全与权限访问
* 默认过滤器:BasicAuthenticationFilter
* 1、oauth_client_details表中clientSecret字段加密【ClientDetails属性secret】
* 2、CheckEndpoint类的接口 oauth/check_token 无需经过过滤器过滤,默认值:denyAll()
* 对以下的几个端点进行权限配置:
* /oauth/authorize:授权端点
* /oauth/token:令牌端点
* /oauth/confirm_access:用户确认授权提交端点
* /oauth/error:授权服务错误信息端点
* /oauth/check_token:用于资源服务访问的令牌解析端点
* /oauth/token_key:提供公有密匙的端点,如果使用JWT令牌的话
**/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
}
二、MyUserDetailsServiceImpl实现UserDetailsService接口。希望用户的信息来自数据库,而不是写死的,所以我们就需要实现UserDetailsService接口
package com.daisyday.manage.auth.service;
import com.daisyday.manage.auth.dao.db1.UserInfoDao;
import com.daisyday.manage.auth.entry.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.User;
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;
/**
* 希望用户的信息来自数据库,而不是写死的,所以我们就需要实现UserDetailsService接口
*/
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserInfoDao userInfoDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo=userInfoDao.selectByUserName(username);
if(userInfo==null){
throw new UsernameNotFoundException("username or password error");
}
String[] permissionArray = new String[1];
permissionArray[0]="admin";
// 将用户名称/用户密码以及用户拥有的权限放入UserDetails中
UserDetails userDetails = User.withUsername(username)
.password(userInfo.getPassword())
.authorities(permissionArray)
.build();
return userDetails;
}
}
三、TokenStoreConfig自定义生成access_token的类型和access_token的存储位置
可以看到access_token有五种生成和存储实现,本项目使用的是JwtTokenStore
package com.daisyday.manage.auth.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.DefaultUserAuthenticationConverter;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* token的保存方式
*
* @author DaisyDay
*/
@Configuration
public class TokenStoreConfig {
private static final String SIGNING_KEY = "auth123456";
@Autowired
private UserDetailsService userDetailsService;
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
userTokenConverter.setUserDetailsService(userDetailsService);
tokenConverter.setUserTokenConverter(userTokenConverter);
jwtAccessTokenConverter.setAccessTokenConverter(tokenConverter);
/**
* 对称秘钥,资源服务器使用该秘钥来验证
*/
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);
return jwtAccessTokenConverter;
}
}
四、MyAuthenticationProvider实现AuthenticationProvider接口来进行自定义认证
package com.daisyday.manage.auth.config;
import com.daisyday.manage.auth.service.MyUserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* AuthenticationProvider方式来进行自定义认证
*/
@Component
public class MyAuthenticationProvider implements AuthenticationProvider {
@Autowired
private MyUserDetailsServiceImpl authUserDetailsService;
/**
* 获取表单提交的信息
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Object credentials = authentication.getCredentials();
String name = authentication.getName();
Object principal = authentication.getPrincipal();
//获取用户信息
UserDetails user = authUserDetailsService.loadUserByUsername(name);
//获取用户权限信息
Collection extends GrantedAuthority> authorities = user.getAuthorities();
return new UsernamePasswordAuthenticationToken(user, principal, authorities);
}
/**
* @Description 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
* @param authentication
* @return
*/
@Override
public boolean supports(Class> authentication) {
return true;
}
}