spring security oauth2授权码模式
这里我使用的spring security
版本为5.x,spring boot
版本为2.x。
授权码模式是Oauth2协议最严格、流程最完整的授权模式,流程如下:
- A.客户端将用户导向认证服务器
- B.用户决定是否给客户端授权
- C.用户同意给客户端授权,认证服务器将用户导向客户端提供的url地址,并返回授权码
- D客户端通过重定向url和授权码想授权服务器申请令牌
- E.认证服务器验证通过后返回令牌给客户端
在A步骤中,向认证服务器请求的url中,应包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为
code
,标识授权码模式 - client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
认证服务器返回授权码后,在D步骤中想认证服务器申请令牌请求中,应包含以下参数:
- grant_type:表示使用的授权模式,必选项,此处的值固定为
authorization_code
。 - code:表示上一步获得的授权码,必选项。
- redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
- client_id:表示客户端ID,必选项。
介绍完认证流程,直接上代码
首先编写认证服务器代码,创建一个spring boot
工程,我这里项目名叫做security-oauth2-server
,端口为8085
,添加依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
在启动类上添加@EnableAuthorizationServer
注解开启认证服务。这里我模拟了从数据库获取用户信息,所以要实现UserDetailsService
,它只有一个抽象方法loadUserByUsername
,它返回的是一个UserDetails
对象,它是一个接口,所以我自定义了一个SysUser
对象实现了它,代码如下
public class SysUser implements UserDetails {
private String username;
private String password;
private boolean accountNonExpired = true;//账户是否为过期
private boolean accountNonLocked = true;//账号是否未锁定
private boolean credentialsNonExpired = true;//凭证是否未过期,也是就是密码
private boolean enabled = true;//账户是否可用
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
GrantedAuthority authority = new SimpleGrantedAuthority("USER");
ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(authority);
return grantedAuthorities;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isAccountNonExpired() {
return accountNonExpired;
}
public void setAccountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
}
public boolean isAccountNonLocked() {
return accountNonLocked;
}
public void setAccountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
}
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
public void setCredentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
因为在返回的对象中,它的属性中所有boolean
类型的值都要为true
,并且权限集合不能为空,所以在这里我给它进行了初始化。然后是编写UserDetailsService
的实现类UserService
类,代码如下:
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUser user = new SysUser();
user.setUsername(username);
user.setPassword(passwordEncoder.encode("root"));
return user;
}
登录的用户名可以是任意的,但是密码一定是root
在这个版本里,用户密码默认是要加密,加密的工具类可以在启动类中注入,代码如下:
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
编写spring security
配置类
@Configuration //开启权限验证
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
编写认证服务配置类
@Configuration
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Bean
public TokenStore tokenStore(){
return new InMemoryTokenStore();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("xj-client")//客户端id
.authorizedGrantTypes("authorization_code","refresh_token")//授权方式
.scopes("all")//授权权限
.secret(passwordEncoder.encode("xj-client"));//客户端密码,要加密
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("permitAll()")
.checkTokenAccess("isAuthenticated()").allowFormAuthenticationForClients();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.userDetailsService(userDetailsService);
}
}
认证服务器的代码到此就编写好了,然后就是编写资源服务器的代码。
创建一个spring boot
工程,我这项目名叫security-resource-server
,访问端口为8084
添加依赖
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置类上添加@EnableResourceServer
开启资源服务,编写application.yml
配置文件
auth-server-url: http://localhost:8085
server:
port: 8084
security:
oauth2:
client:
client-secret: xj-client
client-id: xj-client
scope: all
access-token-uri: ${auth-server-url}/oauth/token #申请令牌地址
user-authorization-uri: ${auth-server-url}/oauth/authorize #用户授权地址
resource:
token-info-uri: ${auth-server-url}/oauth/check_token #检查令牌
编写一个TestController
用于测试
@RestController
public class TestController {
private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
@GetMapping("index")
public Object index(Authentication authentication){
LOGGER.info("resource: user {}",authentication);
return authentication;
}
}
到此,代码就编写好了,可以测试一下。把两个项目都启动,先向授权服务器申请授权码,在浏览器地址栏输入http://localhost:8085/oauth/authorize?response_type=code&client_id=xj-client&redirect_uri=http://baidu.com
输入用户名密码登录,进入到授权页面
选择Approve
,点击Authorize
同意授权,页面会从定向到你提供的uri地址,并在后面附上授权码
然后就可以用这个授权码去授权令牌,打开postman,输入一下内容
使用Basic Auth
登录相当于在请求头中添加一个key
为Authorization
值为client_id:client_secret
结果base64加密后的字符串,下面返回的access_token
的值就是令牌,,现在可以使用这个令牌去资源服务器访问了
查看:完整代码