<modules>
<module>authorization-server</module>
<module>resource-server</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.0.9.RELEASE</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
认证服务器的配置主要包括四点:启用授权服务、客户端详情服务、配置令牌的访问端点和令牌服务、配置令牌端点的安全约束
启用授权服务:用于标识当前服务是一个授权服务 — 在配置类中增加@EnableAuthorizationServer即可
客户端详情服务: 用于配置需要认证的客户端相关信息
// 重写AuthorizationServerConfigurerAdapter类中的configure(ClientDetailsServiceConfigurer clients)方法(注意参数),并在此方法中配置客户端信息。
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory() // 客户端信息存储位置
// 客户端详情
.withClient("client") // 客户端ID
.secret(passwordEncoder.encode("878412")) // 客户端密码
.resourceIds("user") // 客户端允许访问的资源
.redirectUris("http://localhost:8082/test")// 回调地址
.scopes("all")// 授权范围
// .authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token") // 授权类型:授权码模式、密码模式、客户端模式、简化模式
.authorizedGrantTypes("authorization_code") // 授权类型。
.autoApprove(false);
// 可以在and()后面配置另外一个客户端的信息.
}
}
配置令牌的访问端点和令牌服务
// 在TokenConfig中创建令牌的存储策略(jwt令牌)
@Configuration
public class TokenConfig {
private String SIGNING_KEY="snail878412";
@Bean
public TokenStore tokenStore(){
// 令牌存储策略:jwt方式
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);
return jwtAccessTokenConverter;
}
}
//
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
// 令牌管理服务
public AuthorizationServerTokenServices tokenServices(){
DefaultTokenServices services = new DefaultTokenServices();
services.setClientDetailsService(clientDetailsService); // 客户端详情服务器(第2点中已经配好的),自动注入
services.setSupportRefreshToken(true); // 支持刷新令牌
services.setTokenStore(tokenStore); // 令牌存储方式(在TokenConfig中配置的)
// 令牌增强
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
//(在TokenConfig中配置的)
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
services.setTokenEnhancer(tokenEnhancerChain);
services.setAccessTokenValiditySeconds(7200); // 令牌过期时间
services.setRefreshTokenValiditySeconds(259200); // 刷新令牌过期时间
return services;
}
//授权码的存储方式
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
return new InMemoryAuthorizationCodeServices();
}
@Autowired
private AuthorizationCodeServices authorizationCodeServices;
@Autowired
private AuthenticationManager authenticationManager; // 认证管理器自动注入
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// 令牌访问端点,包括:/authrize、/token、/confirm_access、/error、/check_token、/token_key(前缀为/oauth)
endpoints
.authenticationManager(authenticationManager) // 认证管理器(参见后面的说明)
.authorizationCodeServices(authorizationCodeServices) // 授权码服务(参见上面authorizationCodeServices方法的定义)
.tokenServices(tokenServices()) // 令牌管理服务(见上面的方法定义)
.allowedTokenEndpointRequestMethods(HttpMethod.POST);
}
}
认证管理器定义
// 配置SecurityConfig ,并在此配置文件中重写WebSecurityConfigurerAdapter类中的authenticationManagerBean方法创建出AuthenticationManager对象,同时还需要配置UserDetailsService
@Configuration
public class SecurityConfig 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.csrf().disable()
.authorizeRequests()
.antMatchers("/login/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
// 实现 UserDetailsService
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String pwd = passwordEncoder.encode("123456");
return new User(username, pwd,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin,sysadmin"));
}
}
配置令牌端点的安全约束
令牌的端点服务允许如何方法
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()")
.allowFormAuthenticationForClients();
}
}
将客户端和授权码存储在数据库中
sql
DROP TABLE IF EXISTS `oauth_access_token`;CREATE TABLE `oauth_access_token` ( `token_id` varchar(255) DEFAULT NULL COMMENT '加密的access_token的值', `token` longblob COMMENT 'OAuth2AccessToken.java对象序列化后的二进制数据', `authentication_id` varchar(255) DEFAULT NULL COMMENT '加密过的username,client_id,scope', `user_name` varchar(255) DEFAULT NULL COMMENT '登录的用户名', `client_id` varchar(255) DEFAULT NULL COMMENT '客户端ID', `authentication` longblob COMMENT 'OAuth2Authentication.java对象序列化后的二进制数据', `refresh_token` varchar(255) DEFAULT NULL COMMENT '加密的refresh_token的值') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_approvals`;CREATE TABLE `oauth_approvals` ( `userId` varchar(255) DEFAULT NULL COMMENT '登录的用户名', `clientId` varchar(255) DEFAULT NULL COMMENT '客户端ID', `scope` varchar(255) DEFAULT NULL COMMENT '申请的权限范围', `status` varchar(10) DEFAULT NULL COMMENT '状态(Approve或Deny)', `expiresAt` datetime DEFAULT NULL COMMENT '过期时间', `lastModifiedAt` datetime DEFAULT NULL COMMENT '最终修改时间') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_client_details`;CREATE TABLE `oauth_client_details` ( `client_id` varchar(255) NOT NULL COMMENT '客户端ID', `resource_ids` varchar(255) DEFAULT NULL COMMENT '资源ID集合,多个资源时用逗号(,)分隔', `client_secret` varchar(255) DEFAULT NULL COMMENT '客户端密匙', `scope` varchar(255) DEFAULT NULL COMMENT '客户端申请的权限范围', `authorized_grant_types` varchar(255) DEFAULT NULL COMMENT '客户端支持的grant_type', `web_server_redirect_uri` varchar(255) DEFAULT NULL COMMENT '重定向URI', `authorities` varchar(255) DEFAULT NULL COMMENT '客户端所拥有的Spring Security的权限值,多个用逗号(,)分隔', `access_token_validity` int(11) DEFAULT NULL COMMENT '访问令牌有效时间值(单位:秒)', `refresh_token_validity` int(11) DEFAULT NULL COMMENT '更新令牌有效时间值(单位:秒)', `additional_information` varchar(255) DEFAULT NULL COMMENT '预留字段', `autoapprove` varchar(255) DEFAULT NULL COMMENT '用户是否自动Approval操作') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_client_token`;CREATE TABLE `oauth_client_token` ( `token_id` varchar(255) DEFAULT NULL COMMENT '加密的access_token值', `token` longblob COMMENT 'OAuth2AccessToken.java对象序列化后的二进制数据', `authentication_id` varchar(255) DEFAULT NULL COMMENT '加密过的username,client_id,scope', `user_name` varchar(255) DEFAULT NULL COMMENT '登录的用户名', `client_id` varchar(255) DEFAULT NULL COMMENT '客户端ID') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_code`;CREATE TABLE `oauth_code` ( `code` varchar(255) DEFAULT NULL COMMENT '授权码(未加密)', `authentication` blob DEFAULT NULL COMMENT 'AuthorizationRequestHolder.java对象序列化后的二进制数据') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `oauth_refresh_token`;CREATE TABLE `oauth_refresh_token` ( `token_id` varchar(255) DEFAULT NULL COMMENT '加密过的refresh_token的值', `token` longblob COMMENT 'OAuth2RefreshToken.java对象序列化后的二进制数据 ', `authentication` longblob COMMENT 'OAuth2Authentication.java对象序列化后的二进制数据') ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) DEFAULT NULL COMMENT '用户名', `password` varchar(50) DEFAULT NULL COMMENT '密码', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表';
增加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
修改配置
// 改用数据库存储的方式
@Autowired
private DataSource dataSource;
// 客户端jdbc存储方式
@Bean
public JdbcClientDetailsService jdbcClientDetailsService(){
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
jdbcClientDetailsService.setPasswordEncoder(passwordEncoder());
return jdbcClientDetailsService;
}
//授权码的存储方式(数据库)
@Bean
public AuthorizationCodeServices authorizationCodeServices(){
return new JdbcAuthorizationCodeServices(dataSource);
}
// 修改客户端详情的配置(configure(ClientDetailsServiceConfigurer clients)方法)
@Autowired
private JdbcClientDetailsService jdbcClientDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// 客户端详情
clients.withClientDetails(jdbcClientDetailsService);
}
@Configuration
public class TokenConfig {
private String SIGNING_KEY="snail878412";
@Bean
public TokenStore tokenStore(){
// 令牌存储策略:jwt方式
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(SIGNING_KEY);
return jwtAccessTokenConverter;
}
}
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.resourceId("user")
.tokenStore(tokenStore)
.stateless(true);
}
}
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/test/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.csrf()
.disable()
;
}
}
资源
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("getCurrentUser")
public Object getCurrentUser(Authentication authentication){
return authentication.getPrincipal();
}
}