OAuth2.0

Spring Security 和 Shiro

Shiro

Apache Shiro是一个强大且易用的Java安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。

使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

特点

  1. 易于理解的 Java Security API;
  2. 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);
  3. 对角色的简单的签权(访问控制),支持细粒度的签权;
  4. 支持一级缓存,以提升应用程序的性能;
  5. 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
  6. 异构客户端会话访问;
  7. 非常简单的加密 API;
  8. 不跟任何的框架或者容器捆绑,可以独立运行。


Spring Security

Spring Security 主要实现了Authentication(认证,解决who are you? ) 和 Access Control(访问控制,也就是what are you allowed to do?,也称为Authorization)。

Spring Security在架构上将认证与授权分离,并提供了扩展点。

它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成 ,并配备了流行的安全算法实现捆绑在一起。

特点

Shiro能实现的,Spring Security 基本都能实现,依赖于Spring体系,但是好处是Spring全家桶的亲儿子,集成上更加契合,在使用上,比Shiro略负责。

两者对比

Shiro比Spring Security更容易使用,也就是实现上简单一些,同时基本的授权认证Shiro也基本够用.

Spring Security社区支持度更高,Spring社区的亲儿子,支持力度和更新维护上有优势,同时和Spring这一套的结合较好。

Shiro 功能强大、且 简单、灵活。是Apache 下的项目,比较可靠,且不跟任何的框架或者容器绑定,可以独立运行。

推荐

如果开发的项目是Spring这一套,用Spring Security我觉得更合适一些,他们本身就是一套东西,顺畅,可能略微复杂一些,但是学会了就是自己的。

如果开发项目比较紧张,Shiro可能更合适,容易上手,也足够用,Spring Security中有的,Shiro也基本都有,没有的部分网上也有大批的解决方案。

如果项目没有使用Spring这一套,不用考虑,直接Shiro。

先说OAuth,OAuth是Open Authorization的简写。

OAuth协议为用户资源的授权提供了一个安全的、开放而又简易的标准。

与以往的授权方式不同之处是OAuth的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAuth是安全的。

OAuth2.0是OAuth协议的延续版本,但不向前兼容(即完全废止了OAuth1.0)。


OAuth2.0

历史

官方文档解释

看到 Spring 官方对于 Oauth2.0 的解释,他说了这么一句

The Spring Security OAuth project is deprecated. The latest OAuth 2.0 support is provided by Spring Security. See the OAuth 2.0 Migration Guide for further details.

也就是说原来的 Spring Security OAuth2.0 已经废弃了,有关 OAuth2.0 的支持已经集成到了 Spring Security 里面了。

我们看OAuth2.0认证流程:引自OAauth2.0协议rfc6749 https://tools.ietf.org/html/rfc6749


大约十年前,Spring 引入了一个社区驱动的开源项目 Spring Security OAuth,并将其纳入 Spring 项目组合中。到今天,它已经发展成为一个成熟的项目,可以支持大部分 OAuth 规范,包括资源服务器、客户端和授权服务器等。

现在它已成为 UAA(User Account and Authentication Server) 的基础。

Spring Security OAuth 项目已成为一个样板项目,它证明了 Spring 社区可以出色的完成工作。

然而早期的项目存在这样一些问题:

OAuth 是在很早的时候完成的,开发者无法预料未来的变化以及这些代码到底要被怎么用,导致很多 Spring 项目提供了自己的 OAuth 支持,这就带来了 OAuth2 支持的碎片化。

最早的 OAuth 项目同时支持 OAuth1.0 和 OAuth2.0,现在 OAuth1.0 早已经不再使用,可以放弃了。

现在我们有更多的库可以选择,可以在这些库的基础上去开发,以便更好的支持 JWT 等新玩意。

基于以上这些原因,官方决定在社区成功的基础上,重写 Spring Security OAuth,以更好地协调 Spring 和 OAuth,并简化代码库,以使 Spring 的 OAuth 支持更加灵活。

然而,在重写的过程中,发生了不少波折。

2018.01.30

事情得从 2018 年 1 月 30 号讲起。

那天 Spring 官方发了一个通知,说是要逐渐停止现有的 OAuth2 支持,而在 Spring Security5 中构建下一代 OAuth2.0 支持。

为什么要这样呢?

大家知道,OAuth2 只是一种协议,Spring 框架通过代码对这种协议进行落地。

当时 OAuth2 的落地方案比较混乱(这种混乱到今天依然存在),在 Spring Security OAuth、Spring Cloud Security、Spring Boot 1.5.x 以及当时最新的 Spring Security5.x 中都提供了对 OAuth2 的实现。

以至于当开发者需要使用 OAuth2 时,不得不问,到底选哪一个依赖合适呢?

已经有三个地方提供了 OAuth2 的支持,已经够混乱了,为什么还要在最新的 Spring Security5.x 中继续提供实现呢?

所以 Spring 官方决定有必要将 OAuth2.0 的支持统一到一个项目中,以便为用户提供明确的选择并避免任何潜在的混乱,同时 OAuth2 的开发文档也要重新编写,以方便开发人员学习。

所有的决定将在 Spring Security5 中开始,构建下一代 OAuth2.0 的支持。

从那个时候起,Spring Security OAuth 项目就正式处于维护模式。官方将提供至少 1 年的错误/安全修复程序,并且会考虑添加次要功能,但不会添加主要功能。同时将 Spring Security OAuth 中的所有功能重构到 Spring Security5.x 中。

2019.11.14

时间到了 2019.11.14。

这天,官方又发了个通知。

先说了 Spring Security OAuth 在迁往 Spring Security5.x 的过程非常顺利,大部分迁移工作已经完成了,剩下的将在 5.3 版本中完成迁移,在迁移的过程中还添加了许多新功能,包括对 OpenID Connect1.0 的支持

接下来话锋一转,就是将不再提供对授权服务器的支持,不提供的原因,官方给了两个:

在 2019 年,将有大量的商业和开源授权服务器可用。授权服务器是使用一个库来构建产品,而 Spring Security 作为框架,并不适合做这件事情。许多开发者表示对此难以接受。这件事也在 Spring 社区引发了激烈的讨论,好在 Spring 官方愿意倾听来自社区的声音。

2020.04.15

这天,官方又发了个通知。

这次宣布了 Spring Authorization Server 项目。这是一个由 Spring Security 团队领导的社区驱动的项目,致力于向 Spring 社区提供 Authorization Server 支持。

官方倾听了来自社区的声音,决定继续提供授权服务器。这次只是宣布了一下,算是安抚了一下社区的情绪,但是项目还没开发出来。

2020.08.21

Spring Authorization Server 0.0.1 正式发布!

同时公布了项目源码地址:https://github.com/spring-projects-experimental/spring-authorization-server

在这个版本中,主要提供了如下功能:

OAuth 2.0授权代码授予  -RFC 6749
OAuth 2.0客户端凭据授予  -RFC 6749
JSON Web令牌(JWT)  -RFC 7519
JSON Web签名(JWS)  -RFC 7515
JSON Web密钥(JWK)  -RFC 7517

这就是 OAuth2 最近几年的变更之路。

使用场景

假设,A网站是一个打印照片的网站,B网站是一个存储照片的网站,二者原本毫无关联。

如果一个用户想使用A网站打印自己存储在B网站的照片,那么A网站就需要使用B网站的照片资源才行。

按照传统的思考模式,我们需要A网站具有登录B网站的用户名和密码才行,

但是,现在有了OAuth2,只需要A网站获取到使用B网站照片资源的一个通行令牌即可!

这个令牌无需具备操作B网站所有资源的权限,也无需永久有效,只要满足A网站打印照片需求即可。

这么听来,是不是有点像单点登录?

单点登录是用户一次登录,自己可以操作其他关联的服务资源。

OAuth2则是用户给一个系统授权,可以直接操作其他系统资源的一种方式。

但SpringSecurity的OAuth2也是可以实现单点登录的!

总结一句:SpringSecurity的OAuth2可以做服务之间资源共享,也可以实现单点登录!


OAuth2.0中四种授权方式

授权码模式(authorization code)

流程 说明:【A服务客户端】需要用到【B服务资源服务】中的资源

  1. 【A服务客户端】将用户自动导航到【B服务认证服务】,这一步用户需要提供一个回调地址,以备【B服务认证服务】返回授权码使用。
  2. 用户点击授权按钮表示让【A服务客户端】使用【B服务资源服务】,这一步需要用户登录B服务,也就是说用户要事先具有B服务的使用权限。
  3. 【B服务认证服务】生成授权码,授权码将通过第一步提供的回调地址,返回给【A服务客户端】。 注意这个授权码并非通行【B服务资源服务】的通行凭证。
  4. 【A服务认证服务】携带上一步得到的授权码向【B服务认证服务】发送请求,获取通行凭证token。
  5. 【B服务认证服务】给【A服务认证服务】返回令牌token和更新令牌refresh token。

使用场景授权码模式是OAuth2中最安全最完善的一种模式,应用场景最广泛,可以实现服务之间的调用,常见的微信,QQ等第三方登录也可采用这种方式实现。

简化模式(implicit)

流程 说明:简化模式中没有【A服务认证服务】这一部分,全部有【A服务客户端】与B服务交互,整个过程不再有授权码,token直接暴露在浏览器。

  1. 【A服务客户端】将用户自动导航到【B服务认证服务】,这一步用户需要提供一个回调地址,以备【B服务认证服务】返回token使用,还会携带一个【A服务客户端】的状态标识state。
  2. 用户点击授权按钮表示让【A服务客户端】使用【B服务资源服务】,这一步需要用户登录B服务,也就是说用户要事先具有B服务的使用权限。
  3. 【B服务认证服务】生成通行令牌token,token将通过第一步提供的回调地址,返回给【A服务客户端】。

使用场景 适用于A服务没有服务器的情况。比如:纯手机小程序,JavaScript语言实现的网页插件等。

密码模式(resource owner password credentials)

流程

  1. 直接告诉【A服务客户端】自己的【B服务认证服务】的用户名和密码
  2. 【A服务客户端】携带【B服务认证服务】的用户名和密码向【B服务认证服务】发起请求获取token。
  3. 【B服务认证服务】给【A服务客户端】颁发token。

使用场景 此种模式虽然简单,但是用户将B服务的用户名和密码暴露给了A服务,需要两个服务信任度非常高才能使用。

客户端模式(client credentials)

流程 说明:这种模式其实已经不太属于OAuth2的范畴了。A服务完全脱离用户,以自己的身份去向B服务索取token。换言之,用户无需具备B服务的使用权也可以。完全是A服务与B服务内部的交互,与用户无关了。

  1. A服务向B服务索取token。
  2. B服务返回token给A服务。

使用场景 A服务本身需要B服务资源,与用户无关。

实例

使用springboot搭建OAuth2.0认证中心

项目源代码地址:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v2.0.0 (认准v2.0.0标签)

1.引入最核心的三个maven依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

像是mybatis、mybatis plus、fastjson、lombok、thymeleaf 等依赖都是辅助依赖,不赘述。


2.编写启动类

@SpringBootApplication
public class AuthCenterApplication {

    public static void main(String[] args) {
        SpringApplication.run(AuthCenterApplication.class, args);
    }
}

3.配置文件

server:
  port: 30000
spring:
  application:
    name: auth-center
  datasource:
    url: jdbc:mysql://${db}/security?useSSL=false&userUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: ${db_password}
    driver-class-name: com.mysql.jdbc.Driver
  thymeleaf:
    prefix: classpath:/templates/
    suffix: .html
    cache: false

配置完以上三项,就可以将项目正常启动起来了,但是是一个一片空白的项目。


4.EnableAuthorizationServer

可以用 @EnableAuthorizationServer 注解并继承AuthorizationServerConfigurerAdapter来配置OAuth2.0 授权服务器。

在config包下创建AuthorizationServer:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter{
    ......
}

AuthorizationServerConfigurerAdapter要求重写以下三个方法并配置方法中的几个类,

这几个类是由Spring创建的独立的配置对象,它们会被Spring传入AuthorizationServerConfigurer中进行配置。

public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {}
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {}
  • ClientDetailsServiceConfigurer :用来配置客户端详情服务(ClientDetailsService),

    客户端详情信息在这里进行初始化,你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。

  • AuthorizationServerEndpointsConfigurer :用来配置令牌(token)的访问端点和令牌服(tokenservices)。

  • AuthorizationServerSecurityConfigurer :用来配置令牌端点的安全约束。


5.配置客户端详细信息

ClientDetailsServiceConfigurer 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),ClientDetailsService负责查找ClientDetails,而ClientDetails有几个重要的属性如下列表:

  • clientId :(必须的)用来标识客户的Id。
  • secret :(需要值得信任的客户端)客户端安全码,如果有的话。
  • scope :用来限制客户端的访问范围,如果为空(默认)的话,那么客户端拥有全部的访问范围。
  • authorizedGrantTypes :此客户端可以使用的授权类型,默认为空。
  • authorities :此客户端可以使用的权限(基于Spring Security authorities)。

客户端详情(Client Details)能够在应用程序运行的时候进行更新,可以通过访问底层的存储服务(例如将客户端详情存储在一个关系数据库的表中,就可以使用 JdbcClientDetailsService)或者通过自己实现ClientRegistrationService接口(同时你也可以实现 ClientDetailsService 接口)来进行管理。

我们暂时使用内存方式存储客户端详情信息,配置如下:

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() 				//使用in‐memory存储
                .withClient("c1")
                .secret(new BCryptPasswordEncoder().encode("secret"))//$2a$10$0uhIO.ADUFv7OQ/kuwsC1.o3JYvnevt5y3qX/ji0AUXs4KYGio3q6
                .resourceIds("r1")
                .authorizedGrantTypes("authorization_code", "password", "client_credentials", "implicit", "refresh_token")//该client允许的授权类型
                .scopes("all")			//授权范围
                .autoApprove(false)
                .redirectUris("https://www.baidu.com");
    }

6.管理令牌

AuthorizationServerTokenServices 接口定义了一些操作使得你可以对令牌进行一些必要的管理,令牌可以被用来加载身份信息,里面包含了这个令牌的相关权限。

自己可以创建 AuthorizationServerTokenServices 这个接口的实现,则需要继承 DefaultTokenServices 这个类,里面包含了一些有用实现,你可以使用它来修改令牌的格式和令牌的存储。

默认的,当它尝试创建一个令牌的时候,是使用随机值来进行填充的,除了持久化令牌是委托一个 TokenStore 接口来实现以外,这个类几乎帮你做了所有的事情。

并且 TokenStore 这个接口有一个默认的实现,它就是 InMemoryTokenStore ,如其命名,所有的令牌是被保存在了内存中。

除了使用这个类以外,你还可以使用一些其他的预定义实现,下面有几个版本,它们都实现了TokenStore接口:

  • InMemoryTokenStore :这个版本的实现是被默认采用的,它可以完美的工作在单服务器上(即访问并发量压力不大的情况下,并且它在失败的时候不会进行备份),大多数的项目都可以使用这个版本的实现来进行尝试,你可以在开发的时候使用它来进行管理,因为不会被保存到磁盘中,所以更易于调试。
  • JdbcTokenStore :这是一个基于JDBC的实现版本,令牌会被保存进关系型数据库。使用这个版本的实现时,你可以在不同的服务器之间共享令牌信息,使用这个版本的时候请注意把"spring-jdbc"这个依赖加入到你的classpath当中。
  • JwtTokenStore :这个版本的全称是 JSON Web Token(JWT),它可以把令牌相关的数据进行编码(因此对于后端服务来说,它不需要进行存储,这将是一个重大优势),但是它有一个缺点,那就是撤销一个已经授权令牌将会非常困难,所以它通常用来处理一个生命周期较短的令牌以及撤销刷新令牌(refresh_token)。另外一个缺点就是这个令牌占用的空间会比较大,如果你加入了比较多用户凭证信息。JwtTokenStore 不会保存任何数据,但是它在转换令牌值以及授权信息方面与 DefaultTokenServices 所扮演的角色是一样的。

6.1 定义TokenConfig

在config包下定义TokenConfig,我们暂时先使用InMemoryTokenStore,生成一个普通的令牌。

@Configuration 
public class TokenConfig {
    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }
}

6.2 定义ClientDetailsService

由于配置了5,所以这里会spring会帮我们生成一个基于内存的 ClientDetailsService

6.3 定义AuthorizationServerTokenServices

在AuthorizationServer中定义AuthorizationServerTokenServices

@Autowired
private TokenStore tokenStore;

@Autowired
private ClientDetailsService clientDetailsService;

@Bean
public AuthorizationServerTokenServices tokenServices(){
    DefaultTokenServices services = new DefaultTokenServices();
    services.setClientDetailsService(clientDetailsService);
    services.setSupportRefreshToken(true);
    services.setTokenStore(tokenStore);
    services.setAccessTokenValiditySeconds(7200);
    services.setRefreshTokenValiditySeconds(259200);
    return services;
}

7.令牌访问端点配置

AuthorizationServerEndpointsConfigurer 这个对象的实例可以完成令牌服务以及令牌endpoint配置。

7.1 AuthorizationServerEndpointsConfigurer 授权类型

AuthorizationServerEndpointsConfigurer 通过设定以下属性决定支持的授权类型(Grant Types):

  • authenticationManager :认证管理器,当你选择了资源所有者密码(password)授权类型的时候,请设置这个属性注入一个 AuthenticationManager 对象
  • userDetailsService :如果你设置了这个属性的话,那说明你有一个自己的 UserDetailsService 接口的实现,或者你可以把这个东西设置到全局域上面去(例如 GlobalAuthenticationManagerConfigurer 这个配置对象),当你设置了这个之后,那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查,用来确保这个账号是否仍然有效,假如说你禁用了这个账户的话。
  • authorizationCodeServices :这个属性是用来设置授权码服务的(即 AuthorizationCodeServices 的实例对象),主要用于 "authorization_code" 授权码类型模式。
  • implicitGrantService :这个属性用于设置隐式授权模式,用来管理隐式授权模式的状态。
  • tokenGranter :当你设置了这个东西(即 TokenGranter 接口实现),那么授权将会交由你来完全掌控,并且会忽略掉上面的这几个属性,这个属性一般是用作拓展用途的,即标准的四种授权模式已经满足不了你的需求的时候,才会考虑使用这个。

7.2 AuthorizationServerEndpointsConfigurer授权端点

AuthorizationServerEndpointsConfigurer 这个配置对象有一个叫做 pathMapping() 的方法用来配置端点URL链接,它有两个参数:

  • 第一个参数: String 类型的,这个端点URL的默认链接。
  • 第二个参数: String 类型的,你要进行替代的URL链接。

以上的参数都将以 "/" 字符为开始的字符串,框架的默认URL链接如下列表,可以作为这个 pathMapping() 方法的第一个参数:

  • /oauth/authorize :授权端点。
  • /oauth/token :令牌端点。
  • /oauth/confirm_access :用户确认授权提交端点。
  • /oauth/error :授权服务错误信息端点。
  • /oauth/check_token :用于资源服务访问的令牌解析端点。
  • /oauth/token_key :提供公有密匙的端点,如果你使用JWT令牌的话。

综上AuthorizationServerEndpointsConfigurer配置如下

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private AuthorizationCodeServices authorizationCodeServices;

@Bean
public AuthorizationCodeServices authorizationCodeServices(){
    return new InMemoryAuthorizationCodeServices();
}

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    endpoints
        .authenticationManager(authenticationManager)
        .authorizationCodeServices(authorizationCodeServices)
        .tokenServices(tokenServices())
        .allowedTokenEndpointRequestMethods(HttpMethod.POST);

    endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");//自定义授权页面需要
}

上面需要的AuthenticationManager的定义在SpringSecurity的配置中,下面会讲到。

8.令牌端点的安全约束

AuthorizationServerSecurityConfigurer 用来配置令牌端点(Token Endpoint)的安全约束,在AuthorizationServer中配置如下。

@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
    security
        .tokenKeyAccess("permitAll()") //(1)
        .checkTokenAccess("permitAll()")//(2)
        .allowFormAuthenticationForClients();//(3)
}

(1)tokenkey这个endpoint当使用JwtToken且使用非对称加密时,资源服务用于获取公钥而开放的,这里指这个endpoint完全公开。

(2)checkToken这个endpoint完全公开

(3) 允许表单认证

9.web安全配置

这里可以配置安全拦截机制、自定义登录页面、登录失败拦截器等等

在以下的配置中创建了AuthenticationManager bean,这是 7.2 中所需要的。

@Configuration
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    //认证管理器
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/login*","/css/*").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login.html")
                .loginProcessingUrl("/login")
                .failureHandler(myAuthenticationFailureHandler);

    }
}

自定义登陆页面

spring security默认带的登录页面不可修改,加载速度贼慢,原因是使用的css链接是国外的。所以从各方面来说自定义登录页面都是需要的。

1.创建login.html文件

这个非常简单,只需要将spring security加载速度贼慢的那个页面扒下来就好。

项目中代码链接:https://gitee.com/kdyzm/spring-security-oauth-study/blob/master/auth-center/src/main/resources/static/login.html

2.配置拦截规则

WebSecurityConfig下如下设置

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .csrf().disable()
        .authorizeRequests()
        .antMatchers("/login*","/css/*").permitAll()
        .anyRequest().authenticated()
        .and()
        .formLogin()
        .loginPage("/login.html")
        .loginProcessingUrl("/login")
        .failureHandler(myAuthenticationFailureHandler);
}

.antMatchers("/login*","/css/*").permitAll()是必须的,否则没法登陆,会陷入重定向死循环;

.loginPage("/login.html")
.loginProcessingUrl("/login")

这两个必须一起配置,否则会login 404。

3.自定义登陆失败页面

自定义登陆页面之后登录失败的原因就不提示了,这里使用拦截器进行简单的拦截并返回给前端结果(非常丑,但能用)

@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write(JSONObject.toJSONString(exception.getMessage()));
    }
}

自定义授权页面

这里重写该页面,页面代码地址:https://gitee.com/kdyzm/spring-security-oauth-study/blob/master/auth-center/src/main/resources/templates/grant.html

然后配置OAuth访问端点替换掉原来的地址:

endpoints.pathMapping("/oauth/confirm_access","/custom/confirm_access");

同时,由于重写了页面地址,需要实现/custom/confirm_access 接口

@Controller
@SessionAttributes("authorizationRequest")
public class GrantController {

    /**
     * @see WhitelabelApprovalEndpoint#getAccessConfirmation(java.util.Map, javax.servlet.http.HttpServletRequest)
     * @param model
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping("/custom/confirm_access")
    public ModelAndView getAccessConfirmation(Map<String, Object> model, HttpServletRequest request) throws Exception {
        AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");
        ModelAndView view = new ModelAndView();
        view.setViewName("grant");
        view.addObject("clientId", authorizationRequest.getClientId());
        view.addObject("scopes",authorizationRequest.getScope());
        return view;
    }

}

实现UserDetailsService接口

完成以上配置之后基本上已经配置完了,但是还差一点,那就是实现UserDetailsService接口,不实现该接口,会出现后端死循环导致的stackoverflow问题。

为什么要实现该接口?

该接口通过userName获取用户密码信息用于校验用户密码登陆和权限信息等。

@Service
@Slf4j
public class MyUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        TUser tUser = userMapper.selectOne(new LambdaQueryWrapper<TUser>().eq(TUser::getUsername, username));
        if (Objects.isNull(tUser)) {
            throw new UsernameNotFoundException(username + "账号不存在");//return null也可以
        }
        List<String> allPermissions = userMapper.findAllPermissions(tUser.getId());
        String[] array = null;
        if (CollectionUtils.isEmpty(allPermissions)) {
            log.warn("{} 无任何权限", tUser.getUsername());
            array = new String[]{};
        } else {
            array = new String[allPermissions.size()];
            allPermissions.toArray(array);
        }
        return User
                .withUsername(tUser.getUsername())
                .password(tUser.getPassword())
                .authorities(array).build();
    }
}

接口测试

在测试前,需要先执行数据库脚本并启动服务

  1. 执行auth-center/docs/sql/init.sql 文件,创建数据库并创建相关的表
  2. 修改auth-center项目下的配置文件中的数据库连接配置

然后运行 AuthCenterApplication 程序,测试几种oauth认证模式。


参考

posted @ 2022-05-01 14:20  Ricardo_ML  阅读(183)  评论(0编辑  收藏  举报