spring boot oauth2服务端搭建
相关网站
官方文档首页:https://projects.spring.io/spring-security-oauth/docs/Home.html
源代码(oauth):https://github.com/spring-projects/spring-security-oauth
源代码(boot):https://github.com/spring-projects/spring-security-oauth2-boot
开发指南:https://projects.spring.io/spring-security-oauth/docs/oauth2.html
注:新版本的spring-security已对oauth2做了较大改变,整个集成过程已经完全不一样,学习过程中注意相关版本号
集成过程
- 搭建基本的spring-boot项目,这里不再重复,其中涉及的软件版本
<dependencyManagement>
<dependencies>
<dependency>
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!--安全框架 start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!--安全框架 end-->
</dependencies>
- 授权服务器配置,最简化配置,先让程序能跑起来,后期再扩展
@Component
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("ifusespasswordgranttype").authorizedGrantTypes("password")
.secret("{noop}thenneedsauthenticationmanager").scopes("any");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
private static String CLIENT = "ifusespasswordgranttype:thenneedsauthenticationmanager";
public static void main(String[] args) {
byte[] encodedAuth = Base64.encodeBase64(CLIENT.getBytes());
String authHeader = "Basic " + new String(encodedAuth);
System.out.println(authHeader);
}
}
说明:
- 授权服务器client配置
- 访问权限配置
- 授权管理器配置,见SecurityConfig
- main函数提供client加密算法,该解决需要请求时写到header中,如:Authorization=Basic xxxx
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean(BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
@Bean
public UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(
User.withUsername("admin1").password("{noop}111111").roles("USER").build()
);
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
public static void main(String[] args) {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encode1 = passwordEncoder.encode("{MD5}admin");
String encode2 = passwordEncoder.encode("{MD5}111111");
System.out.println(encode1 + " " + encode2);
boolean root = passwordEncoder.matches("{MD5}111111", encode1);
boolean root1 = passwordEncoder.matches("{MD5}111111", encode2);
System.out.println(root + " " + root1);
}
}
说明:
- 覆盖授权管理器
- 覆盖用户管理器
- main函数提供密码加密及验证方法
个性定制
UserDetailsService动态取用户信息
@Component
public class UserDetailsServiceEnergy implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> role_user = AuthorityUtils.createAuthorityList("ROLE_USER");
SecurityUserDetails userDetails = new SecurityUserDetails(username, "{bcrypt}$2a$10$bzDNJk/XW67geVlpjQl3uuppRSsUqK7Gdw5NZbQQk/eTjHUV77n9S", role_user, 1L);
return userDetails;
}
public static void main(String[] args) {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encode1 = passwordEncoder.encode("{MD5}admin");
String encode2 = passwordEncoder.encode("{MD5}111111");
System.out.println(encode1 + " " + encode2);
boolean root = passwordEncoder.matches("{MD5}111111", encode1);
boolean root1 = passwordEncoder.matches("{MD5}111111", encode2);
System.out.println(root + " " + root1);
}
}
说明:
- 这里将扩展单独抽离出来了,SecurityConfig 中UserDetailsService的配置需要取消
- 命名不用参考,取名字太难了
- SecurityUserDetails继承至org.springframework.security.core.userdetails.User可以附带一些业务信息,具体业务信息自行定制,该实例中附带了用户在数据库中的ID,为了简化代码,这里没有实际操作数据库
- security约定在权限前面必须加“ROLE_”与授权服务器中的roles匹配
- 密码从之前的明文改为了MD5加密,登录时密码格式为:{MD5}111111
ClientDetailsService动态取client信息
@Component
public class ClientDetailsServiceEnergy implements ClientDetailsService {
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
List<String> scope = new ArrayList<>();
scope.add("any");
List<String> grantTypes = new ArrayList<>();
grantTypes.add("password");//支持password授权模式
grantTypes.add("client_credentials");//支持client_credentials授权模式
BaseClientDetails clientDetails = new BaseClientDetails();
clientDetails.setClientId("ifusespasswordgranttype");
clientDetails.setClientSecret("{noop}thenneedsauthenticationmanager");
clientDetails.setScope(scope);
clientDetails.setAccessTokenValiditySeconds(36000);
clientDetails.setAuthorizedGrantTypes(grantTypes);
clientDetails.setAutoApproveScopes(scope);
return clientDetails;
}
private static String CLIENT = "ifusespasswordgranttype:thenneedsauthenticationmanager";
public static void main(String[] args) {
byte[] encodedAuth = Base64.encodeBase64(CLIENT.getBytes());
String authHeader = "Basic " + new String(encodedAuth);
System.out.println(authHeader);
}
}
说明:
- 该扩展可用于API授权登录方式,与用户登录区别,提高账号安全
- 该扩展需要AuthorizationServerConfig中相关配置
- ClientDetailsService 注入时需要指定名称,不指定会报错
扩展完成后的完整代码1.0
注:其他未贴出的代码与上面保持一致
@Component
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Qualifier("clientDetailsServiceEnergy")
@Autowired
private ClientDetailsService clientDetailsService;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
// clients.inMemory().withClient("ifusespasswordgranttype").authorizedGrantTypes("password")
// .secret("{noop}thenneedsauthenticationmanager").scopes("any");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()")
//允许client_credentials方式授权
.allowFormAuthenticationForClients();
}
}
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
@Bean(BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
// @Override
// @Bean
// public UserDetailsService userDetailsService() {
// return new InMemoryUserDetailsManager(
// User.withUsername("admin1").password("{noop}111111").roles("USER").build()
// );
// }
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
登录方式
密码登录
header:
- Authorization=Basic aWZ1c2VzcGFzc3dvcmRncmFudHR5cGU6dGhlbm5lZWRzYXV0aGVudGljYXRpb25tYW5hZ2Vy
form:
- grant_type=password
- username=admin2
- password={MD5}111111
client登录方式
form:
- grant_type=client_credentials
- client_id=ifusespasswordgranttype
- client_secret=thenneedsauthenticationmanager
注:到这里整个集成基本完成,剩下的工作是完善各种错误处理及异常提示
注:前端多系统采用事件通知的方式可实现token共享,达到单点登录目的