本文主要讲解了Springboot集成Security Oauth2的一个简单例子,通过该例子进而学习该方面的知识。
内容简介:使用password密码授权方式,让用户通过账号密码去服务器获取token,并通过token来访问接口。
技术:springboot2.x ,springsecurity5.x
1.pom.xml
org.springframework.boot
spring-boot-starter-parent
2.1.2.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.security.oauth
spring-security-oauth2
2.3.4.RELEASE
2.配置认证授权服务器
使用@EnableAuthorizationServer 注解来配置OAuth2.0 授权服务机制。
通过使用@Bean注解的几个方法一起来配置这个授权服务,这几个配置是由Spring创建的独立的配置对象,它们会被Spring传入AuthorizationServerConfigurer中:
ClientDetailsServiceConfigurer:用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束.
AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
以上的三个配置可以选择继承AuthorizationServerConfigurerAdapter并且覆写其中的三个configure方法来进行配置。
代码如下:
import org.springframework.beans.factory.annotation.Autowired;
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.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;
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
/**
* 用来配置令牌端点(Token Endpoint)的安全约束.
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.realm("oauth2-resource")
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()")
.allowFormAuthenticationForClients();
}
/**
* 用来配置客户端详情服务(ClientDetailsService),客户端详情信息在这里进行初始化,能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
* clientId:(必须的)用来标识客户的Id。
* secret:(需要值得信任的客户端)客户端安全码,如果有的话。
* redirectUris 返回地址,可以理解成登录后的返回地址,可以多个。应用场景有:客户端swagger调用服务端的登录页面,登录成功,返回客户端swagger页面
* authorizedGrantTypes:此客户端可以使用的权限(基于Spring Security authorities)
authorization_code:授权码类型、implicit:隐式授权类型、password:资源所有者(即用户)密码类型、
client_credentials:客户端凭据(客户端ID以及Key)类型、refresh_token:通过以上授权获得的刷新令牌来获取新的令牌。
* scope:用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
* accessTokenValiditySeconds token有效时长
* refreshTokenValiditySeconds refresh_token有效时长
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("demoClient")
.secret(new BCryptPasswordEncoder().encode("demoSecret"))
.redirectUris("")
.authorizedGrantTypes("authorization_code","client_credentials", "password", "refresh_token")
.scopes("all")
.resourceIds("oauth2-resource")
.accessTokenValiditySeconds(60*60)
.refreshTokenValiditySeconds(60*60);
}
/**
* 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
* 访问地址:/oauth/token
* 属性列表:
* authenticationManager:认证管理器,当你选择了资源所有者密码(password)授权类型的时候,需要设置为这个属性注入一个 AuthenticationManager 对象。
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//设置认证管理器
endpoints.authenticationManager(authenticationManager);
//设置访问/oauth/token接口,获取token的方式
endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
}
}
3.配置Spring Security
这里通过继承WebSecurityConfigurerAdapter来创建 WebSecurityConfigurer的实例,重写该类的某些方法,即可配置拦截URL、设置用户权限等安全控制。
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
/*
* Spring Security默认是禁用注解的,要想开启注解(如 @PreAuthorize等注解)
* 需要在继承WebSecurityConfigurerAdapter,并在类上加@EnableGlobalMethodSecurity注解,并设置prePostEnabled 为true
*/
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//配置用户存储在内存中,可以设置用户账号、密码、角色等
@Override
protected UserDetailsService userDetailsService(){
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("user").password(new BCryptPasswordEncoder().encode("123456"))
.roles("USER").build());
manager.createUser(User.withUsername("admin").password(new BCryptPasswordEncoder().encode("123456"))
.roles("ADMIN").build());
return manager;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//将用户信息添加认证管理中,这里有两种方式
//方法一:
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
//方法二:
//auth.inMemoryAuthentication().withUser("user1").password(passwordEncoder().encode("123456")).roles("user");
}
/*
* 进行HTTP配置拦截,过滤url,这里主要配置服务端的请求拦截
* permitAll表示该请求任何人都可以访问,authenticated()表示其他的请求都必须要有权限认证
* 本例子中,以下配置用不到,可以省略,因为本文使用了4中ResourceServerConfig,
* 导致该例子会用ResourceServerConfig中的拦截配置
@Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.and().logout().logoutUrl("/logout").deleteCookies("JSESSIONID").permitAll()
.and().authorizeRequests()
.antMatchers("/login.html").permitAll()
.anyRequest().authenticated()
.and().csrf().disable();
}
*/
/**
* spring5.x 需要注入一个PasswordEncoder,否则会报这个错There is no PasswordEncoder mapped for the id \"null\"
* 加了密码加密,所有用到的密码都需要这个加密方法加密
* @return
*/
@Bean
PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 需要配置这个支持password模式
*/
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
import org.springframework.context.annotation.Configuration;
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;
@Configuration
@EnableResourceServer
/**
* 这里设置访问路径权限,相当于客户端url权限拦截,是可以单独独立出来
*/
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("ADMIN","USER")
.antMatchers("/test").authenticated()
.anyRequest().authenticated();
}
}
5.控制类
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("")
public class MyController {
@RequestMapping(value = "/test",method = RequestMethod.GET)
public Object test(){
return "test";
}
@RequestMapping(value = "/user",method = RequestMethod.GET)
@PreAuthorize("hasAnyRole('ROLE_USER')") //注解验证时默认加上前缀ROLE_,原因后面文章再讲
public Object user(){
return "user";
}
@RequestMapping(value = "/user/a",method = RequestMethod.GET)
public Object user2(){
return "user";
}
@PreAuthorize("hasAnyRole('ROLE_ADMIN')")
@RequestMapping(value = "/admin",method = RequestMethod.GET)
public Object admin(){
return "admin";
}
@RequestMapping(value = "/admin/b",method = RequestMethod.GET)
public Object admin2(){
return "admin";
}
}
6.启动启动类,进行测试
本文使用password授权类型来访问获取token,接口为/oauth/token,请求方式可以为POST和GET。
请求参数和返回结果如下,返回结果中access_token就是我们想要的token了,token_type为token类型,expires_in为有效时长,单位为秒,scope为作用范围,refresh_token在授权类型为refresh_token方式中用于获取新token。
该url请求需要的参数值和部分返回值的值在步骤2的configure(ClientDetailsServiceConfigurer clients)方法中配置。
不带token的接口访问测试,直接返回未授权
带上token进行接口测试,这里展示两种带上token的方式:
第一种:在header上添加一个属性,key为Authorization,value为Bearer+空格+token(如Bearer xxx),注意中间空格
第二种:在请求url后面添加一个属性access_token,值为token,如localhost:8888/test?access_token=6b9159cd-0e33-48ae-94bb-2139a27c0c4c
继续进行测试,这里使用第一种带上token方法,在header带上token,请求访问成功
如果token不正确,则返回结果invalid_token,说明token不正确
继续测试,访问/user,返回无权限。这是由于该token代表的账号的角色为ADMIN,而这个/user需要USER角色才能访问。
这个设置url需要某角色才能访问,上面都讲过。一种为@PreAuthorize(“hasAnyRole(‘ROLE_USER’)”),在控制类方法上标明,这里需要注意的是需要在角色前面加上前缀ROLE_,原理后面文章为讲。另外一种是.antMatchers("/admin/**").hasRole(“ADMIN”) ,这里就不需要前缀了。
可改进地方:本文中用户信息和token都存在于内存中,不适用于项目环境,下一篇文章将讲解用户信息存在于mysql,token存在于redis中。
讲到这里,本篇文章到此也就结束了,如果文章中有问题,或者有一些不够严谨完善的地方,希望大家体谅体谅。欢迎大家留言,交流交流。
最后附上本项目github地址