Spring Security OAuth2实战
0 Views spring-boot with
本文字数:2,177 字 | 阅读时长 ≈ 9 min

Spring Security OAuth2实战

0 Views spring-boot with
本文字数:2,177 字 | 阅读时长 ≈ 9 min

在前面我们讲了:Spring Security OAuth2概念引入 这次我们讲解如何在Spring Boot中使用Spring Security OAuth2搭建权限脚手架项目。

:tada: :tada: :tada: 这里有丰富的 Spring 框架学习案例

仓库地址:spring-learn
欢迎star、fork,给作者一些鼓励

写在前面

如果你还不了解OAuth2,可以去查阅一些资料:

本次博文使用SpringBoot-2.x 和 Spring-Security-oauth2-2.x

引入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<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.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Security配置

通常,我们直接创建一个配置类继承WebSecurityConfigurerAdapter类,重写其中的配置方法即可,并且需要@EnableWebSecurity注解标记即可:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("user_1").password("123456").authorities("USER").build());
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/hello")
                .permitAll()
                .anyRequest()
                .authenticated()

                .and()
                .csrf().disable();
    }

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
}

注意

Security-OAuth2配置

Spring Security OAuth2概念引入一文中我们已经了解了OAuth2的两个核心就是:1.认证服务器、2.资源服务器。那么这里就是配置OAuth2的认证服务器AuthorizationServer

通常我们直接创建一个配置类继承AuthorizationServerConfigurerAdapter类,并重写其中的方法完成配置,并且用@EnableAuthorizationServer注解标记即可:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("client_1")
                .resourceIds("info")
                .authorizedGrantTypes("client_credentials", "password", "refresh_token")
                .scopes("select")
                .authorities("client")
                .secret("123456");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("permitAll()");
    }
}

注意

Resource Server配置

上面Security-OAuth2配置,其实也就是Authentication Server认证服务的配置,但截至到目前,我们还未考虑过Resource Server资源服务配置,但对于我们目前的业务而言也不需要对Resource Server做什么自定义配置,如果需要按照Authentication Server的配置类似食用即可:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}

PasswordEncoder配置

你可能会在搭建上述环境后启动项目还是报错:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

原因就是在Spring Security新版本中,必须手动配置PasswordEncoder密码校验服务,在 SpringBoot整合SpringSecurity 一文中也讲过。所以这里我们简单配置一个类,不做密码加密

@Component
public class PasswordEncoderImpl implements PasswordEncoder {
    @Override
    public String encode(CharSequence charSequence) {
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return s.equals(charSequence.toString());
    }
}

测试

模拟创建Controller接口:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, this is test interface";
    }

    @GetMapping("/info/{name}")
    public String info(@PathVariable String name) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return "hello " + name + ", this your info";
    }
}

访问localhost:8080/hello:

不拦截,正常。

访问:localhost:8080/info/tycoding:

为授权,被拦截,正常。

/oauth/token获取Token

在Postman上测试localhost:8080/oauth/token?username=user_1&password=123456&grant_type=password接口:

可以看到依然被拦截,这是因为Spring Security配置中默认食用Basic认证方式。我们直接将这个请求粘贴到浏览器上:

要解决这个问题,Postman提供模拟Basic登陆的效果:

使用Token访问受保护的资源

按照上述方式获取到了access_token值,在浏览器上直接访问localhost:8080/info/tycoding?access_token=xxx:

看到,还是被拦截,按道理,既然第三方应用拿到了Token值,通过这个Token值应该可以获取到受保护的资源。

这时就需要配置Resource Server了,前面我们仅仅提到了,并没有配置,那么这里创建Resource Server的配置:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}

如果启用了@EnableResourceServer的配置,那么应该重写configure(HttpSecurity http)方法,和@EnableWebSecurity标记类的配置类似,我们这里要配置不需要拦截的请求。如果你仅仅是在@EnableWebSecurity配置类中配置了/hello请求不拦截,而你启用了@EnableResourceServer配置,但并没有配置HttpSecurity的过滤配置,那么默认OAuth还是拦截所有,直接访问/hello反而也被拦截到:

所以,应该配置为:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/hello")
                .permitAll()
                .anyRequest()
                .authenticated()

                .and()
                .csrf().disable();
    }
}

重新运行项目,获取到Token值,直接去浏览器访问:localhost:8080/info/tycoding?access_token=xx:

出现这个错误可能是你的Token值需要更新了。

正常访问

/oauth/check_token

这个接口用于校验此token是否过期

如果错误或过期:

如果正确为过期:

显示该Token对应第三方应用和用户的数据。

Error

以下是我们在整合Spring Security OAuth2常见的错误:

Request method 'GET' not supported”

{
  "error": "method_not_allowed",
  "error_description": "Request method &#39;GET&#39; not supported"
}
  1. 错误描述

通常是因为我们直接使用浏览器请求,那么就采用的GET请求

  1. 解决方案

使用Postman等工具测试接口,发送POST请求即可

Unsupported grant type: password

{
    "error": "unsupported_grant_type",
    "error_description": "Unsupported grant type: password"
}
  1. 错误描述

出现这个错误的原因通常是因为你没有配置支持OAuth2密码模式,这个常出现

  1. 解决方案

Spring Security配置中将AuthenticationManager类注入到Spring IOC容器中:

@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
    return super.authenticationManager();
}

Security OAuth认证服务器(@EnableAuthorizationServer)中配置AuthenticationManager:

@Autowired
private AuthenticationManager authenticationManager;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints.authenticationManager(authenticationManager);
}

Missing grant type

{
    "error": "invalid_request",
    "error_description": "Missing grant type"
}
  1. 错误描述

通产是请求URL中没有配置grant_type的值

  1. 解决方案

请求中配置grant_type类型,比如:grant_type:password

401 Unauthorized

{
    "timestamp": "2019-04-22T07:18:52.341+0000",
    "status": 401,
    "error": "Unauthorized",
    "message": "Unauthorized",
    "path": "/oauth/token"
}
  1. 错误描述

如果是使用的Postman工具请求的URL,报这个错误,通常是因为配置的Spring Security拦截了这个请求,并启用了Basic的验证方式,我们直接将这个URL放在浏览器上访问,则会弹出Basic密码验证框:

正确输入客户端(Client)的账户(clientId)、密码(secret),即可访问。

  1. 解决方案

要解决这个问题,就要考虑怎么能让Postman请求不经过Basic登录框这个验证方式,这就需要知道Basic的加密方式。其实Basic登录框最终是将输入的用户名和密码通过Base64加密,然后在前面加上Basic base64,然后去请求这个URL接口,所以我们需要在 在线加密解密网站 将Client用户名和密码按照:name:password格式加密,最终在Postman中设置请求头Authorization: Basic xxx,这个xxx是按照name:password格式加密的数据:

Full authentication is required to access this resource

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource"
}
  1. 错误描述

如果此时后台也报错:There is no PasswordEncoder mapped for the id "null",通常就是因为没有配置密码校验服务的原因。

产生这个错误的原因有很多

  1. 解决方案

注入任意一个实现了PasswordEncoder接口的实现类。

交流

以上仅是个人见解,欢迎提出意见或建议。

QQ交流群:671017003 欢迎各位前辈或萌新入驻


联系

If you have some questions after you see this article, you can contact me or you can find some info by clicking these links.

如果你觉得这篇文章帮助到了你,你可以帮作者买一杯果汁表示鼓励