0. 概述
根据之前的介绍,授权码模式要先获取授权码,然后再根据授权码获取token,最后根据token获取服务器端的资源。
1. 项目依赖
-
新建一个项目命名为oauth2-server
-
添加如下依赖
parent
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR5</spring-cloud.version>
</properties>
spring security和oauth关键依赖
<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.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
application.properties
server.port=8092
spring.application.name=oauth2-server
2. 添加用户服务来获取授权码
- UserService
/**
* Created by KG on 2019/9/30
*/
@Service
public class UserService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
private List<User> userList;
@PostConstruct
public void initData() {
String password = passwordEncoder.encode("123456");
userList = new ArrayList<>();
userList.add(new User("kg", password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin")));
userList.add(new User("kelvin", password, AuthorityUtils.commaSeparatedStringToAuthorityList("client")));
userList.add(new User("david", password, AuthorityUtils.commaSeparatedStringToAuthorityList("client")));
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<User> findUserList = userList.stream().filter(user -> user.getUsername().equals(username)).collect(Collectors.toList());
if (!CollectionUtils.isEmpty(findUserList)) {
return findUserList.get(0);
} else {
throw new UsernameNotFoundException("用户名或密码错误");
}
}
}
User
/**
* Created by KG on 2019/8/29
*/
public class User implements UserDetails {
private String username;
private String password;
private List<GrantedAuthority> authorities;
public User(String username, String password,List<GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
代码解释:
由于是演示,我们使用了一个基于内存的硬编码的用户列表。
3. 配置认证服务器
- 新建一个config包,在config包下新建类AuthorizationServerConfig继承AuthorizationServerConfigurerAdapter
AuthorizationServerConfig
/**
* 认证服务器配置
* Created by KG on 2019/9/30
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
/**
* 使用用户服务来控制认证服务器的访问
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("admin")//配置client_id
.secret(passwordEncoder.encode("admin123456"))//配置client-secret
.accessTokenValiditySeconds(3600)//配置访问token的有效期
.refreshTokenValiditySeconds(864000)//配置刷新token的有效期
.redirectUris("http://www.baidu.com")//配置redirect_uri,用于授权成功后跳转
.scopes("all")//配置申请的权限范围
.authorizedGrantTypes("authorization_code", "password");//配置grant_type,表示授权类型
}
}
代码解释:
在endpoints的配置中,我们正好利用了之前编写的用户服务,用来控制对认证服务器的访问权限。
在第二个configure中我们配置了client_id和client_secrect,因为要获取token,光靠授权码还是不够的。
4. 安全路由配置
SecurityConfig
/**
* SpringSecurity配置
* Created by KG on 2019/10/8
*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/oauth/**", "/login/**", "/logout/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll();
}
}
代码解释:
我们需要配置一些例外路由,例如oauth的还有登录和登出,它们都不需要在安全框架控制之内,否则就访问不了了。
5. 资源服务器配置
ResourceServerConfig
/**
* 资源服务器配置
* Created by KG on 2019/9/30
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.requestMatchers()
.antMatchers("/user/**");//配置需要保护的资源路径
}
}
代码解释:
我们添加了一个需要保护的资源路由,/user/**,我们会在稍后加上这个资源的实现。
6. 保护资源实现
UserController
/**
* Created by KG on 2019/9/30
*/
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication) {
return authentication.getPrincipal();
}
}
7. 运行步骤1 - 获取认证服务器授权并获取授权码
- 启动oauth2-server服务;
- 在浏览器访问该地址进行登录授权:http://localhost:8092/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all&state=normal
-
输入账号密码进行登录操作:
获取授权码的账号和密码
授权码地址:
https://www.baidu.com/?code=LV2Qfu&state=normal
授权码:
LV2Qfu
8. 运行步骤2 - 通过client_id,client_secret, 授权码请求token
-
使用授权码请求该地址获取访问令牌:http://localhost:8092/oauth/token
-
使用Basic认证通过client_id和client_secret构造一个Authorization头信息;
构造请求头信息 -
在body中添加以下参数信息,通过POST请求获取访问令牌
请求体信息
令牌返回结果:
{
"access_token": "3a586c5d-a707-4ce0-8b0e-e789058f92f5",
"token_type": "bearer",
"expires_in": 3599,
"scope": "all"
}
Token:
3a586c5d-a707-4ce0-8b0e-e789058f92f5
9. 运行步骤3 - 通过token访问资源
- 通过Bearer Token的认证模式来构造一个请求头
好了,一个简易的通过授权码和认证服务器来访问资源的模式就完成了。
网友评论