之所以想写这一系列,是因为之前工作过程中有几次项目是从零开始搭建的,而且项目涉及的内容还不少。在这过程中,遇到了很多棘手的非业务问题,在不断实践过程中慢慢积累出一些基本的实践经验,认为这些与业务无关的基本的实践经验其实可以复刻到其它项目上,在行业内可能称为脚手架,因此决定将此java基础脚手架的搭建总结下来,分享给大家使用。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中主要使用基本框架是 spring-boo-2.3.12.RELEASE和spring-cloud.-Hoxton.SR12,所有代码都在commonFramework项目上:https://github.com/forever1986/commonFramework/tree/master
目录
- 1 相关知识
- 1.1 spring security
- 1.2 JWT
- 1.3 OAuth2
- 2 OAuth2概述
- 2.1 OAuth2的基本概念
- 2.2 OAuth2的模式
- 3 授权服务器-代码实践
- 4 资源服务器-代码实践
- 4.1 资源服务器的原理
- 4.2 操作步骤(采用yml+注解方式)
- 4.3 权限配置说明
1 相关知识
注意:在看本章之前,最好具备spring security和JWT相关知识
在本章java脚手架中,主要来谈谈统一登录问题,统一登录也就是一次登陆多处可用。这个在大部分平台上面都是适用的。那么目前比较好用的就是OAuth2。这里不会详细的讲OAuth2的内容,因为这一部分内容过多。这里只是为了实践,不过会将一些基本概念,作为你了解项目脚手架的基础。在了解OAuth2之前,需要先了解2个基本东西,分别是:spring security和JWT
1.1 spring security
我们后端一般需要鉴权的,那么就有认证和鉴权2步操作。认证无非就是登录,鉴权无非就是判断是否已经登陆过及其权限。后端实现这种框架的有Shiro、spring security等。这里讲一下spring security,原先这个框架学习成本较高,但是只从与spring-boot集成之后,很多都是默认配置,因此学起来较容易。
spring security,简单的理解就是加入spring security的话,访问你后端的服务将会进入认证和鉴权。至于如何认证,spring security集成本身有一套默认流程,还自动集成了登录页面(当然,前后端分离可以改为通过json方式传输)。这里就不详细说明,大家最好先在了解spring security这一块的内容。
其实真正了解之后,会发现spring security与oauth其实必然的关系,spring security是一个认证鉴权的框架,而oauth是一个统一授权机制,是一种授权的协议。之所以让大家先了解spring security,是因为很多时候系统的认证和鉴权都是基于spring security来做的,而oauth其中通过密码认证的模式就可以依赖于spring security(当然你不使用也行)。
1.2 JWT
最开始的前后端分离之间的交互是通过保持状态的session方式,也就服务器通过生成session信息保存在服务器中并返回给客户端,客户端(通常是浏览器)将信息存入cookie里面,每次访问都是拿着cookie访问(一般浏览器自动完成),服务器通过客户端传输的信息与服务器的session进行比对验证。因为session属于有状态的,因此有这么2个大缺点:
1)服务器要存储session信息,如果并发量较大,对于服务器来说需要占用很大内存
2)session信息默认存储在服务器上,这对于集群部署来说就不适用,还需要一个同一个存储的地方,比如redis
基于以上2个缺点,JWT就出现了。
- JWT是一种无状态,也就是认证的时候,服务器给客户端一个token之后,服务器无需保留token
- 由于无状态,因此解决集群部署问题,客户端携带token的访问,在任何服务器都可以鉴权
- JWT包括3部分:header(声明和加密算法)、Payload(有效数据)、Signature(签名,是整个数据的认证信息)。
基于以上的优点,因此它非常适合于统一登录的情况,通过OAuth授权机制,结合JWT的token特性,能很好实现统一认证授权的流程。
1.3 OAuth2
关于OAuth2的形象比喻,我觉得阮一峰大佬那个关于快递员如何进入小区门禁的比喻很是恰当,大家可以搜索一下。这里用通俗的话说一下OAuth2:OAuth 是一个开放标准,该标准允许用户让第三方应用访问该用户在某一网站上存储的私密资源(如头像、照片、视频等),而在这个过程中无需将用户名和密码提供给第三方应用。实现这一功能是通过提供一个令牌(token),而不是用户名和密码来访问他们存放在特定服务提供者的数据。采用令牌(token)的方式可以让用户灵活的对第三方应用授权或者收回权限。OAuth2 是 OAuth 协议的下一版本,但不向下兼容 OAuth 1.0。
我们在很多网站上面看到,我们无需注册,只需要通过github、Microsoft、Google、微信、支付宝等认证,就能够登录某些网站。其实很多认证机制就是基于OAuth授权机制完成的。我们设定一个demo场景来更深刻理解oauth:我们开发一个网站,想通过github的统一认证之后,我们获取用户一些信息,自动注册,用户访问我们网站无需登录和注册,只需要一个授权页面(github提供的)进行确认后,就能登录我们网站。这个过程:
参考:auth-github子模块
2 OAuth2概述
虽然是实践代码,但是最好了解一下OAuth2的基本内容,如果已经了解过,请跳过这部分,直接看代码实践
2.1 OAuth2的基本概念
OAuth2.0 的运行流程中,会涉及到一些名词、概念,熟悉这些名词、概念有助于更好的理解OAuth 2.0 机制,这里我们先了解一下:
- 客户端(Client):请求访问资源的第三方应用;客户端可以是Web站点、App、设备等。上面的demo例子就是我们开发的网站
- 授权服务器(Authorization Server):授权服务器是用于处理和发放访问令牌的服务器。当用户请求访问资源时,需要先向授权服务器请求访问令牌。上面的demo例子就是github
- 资源服务器(Resource Server):资源服务器是用于存储和管理资源的服务器;当用户拥有访问令牌后,就可以向资源服务器请求访问资源。上面的demo例子就是github,我们网站从github上面获得用户信息。(注意:授权服务器和资源服务器可以分开的)
- 资源所有者(Resource Owner):资源所有者通常就是指用户,也就使用我们网站的用户。
- 访问令牌(Access Token):访问令牌是授权服务器发放给客户端的一个凭证,表示客户端有权访问资源所有者的资源。访问令牌有一定的有效期,过期后需要使用刷新令牌来获取新的访问令牌。
- 刷新令牌(Refresh Token):刷新令牌是授权服务器在发放访问令牌时一同发放的一个凭证,用于在访问令牌过期后获取新的访问令牌。刷新令牌通常有较长的有效期,甚至可以设置为永不过期。
2.2 OAuth2的模式
OAuth2有常见的4种模式,当然你也可以定义自身的模式:
- 授权码模式:常见的第三方平台登录功能基本都是使用这种模式。(上面github的demo例子就是这种模式)
- 简化模式:简化模式是不需要客户端服务器参与,直接在浏览器中向授权服务器申请令牌(token),一般如果网站是纯静态页面则可以采用这种方式。
- 密码模式:密码模式是用户把用户名密码直接告诉客户端,客户端使用说这些信息向授权服务器申请令牌(token)。这需要用户对客户端高度信任,例如客户端应用和服务提供商就是同一家公司,我们自己做前后端分离登录就可以采用这种模式。
- 客户端模式:客户端模式是指客户端使用自己的名义而不是用户的名义向服务提供者申请授权,严格来说,客户端模式并不能算作 OAuth 协议要解决的问题的一种解决方案,但是,对于开发者而言,在一些前后端分离应用或者为移动端提供的认证授权服务器上使用这种模式还是非常方便的。
注意:必须先了解各种模式的详细流程,因为不同模式的配置和授权流程都不一样
3 授权服务器-代码实践
在实际应用中,会搭建一个spring security+oauth2+JWT的授权服务器,用于统一授权。其中spring security作为登录使用,oauth2用于统一授权,JWT用于token。
参考子模块auth-authentication
由于代码过多,这里不一一列举,只讲几个重要的点:
1)引入的包:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency><!-- SpringSecurity依赖,用于做登录认证-->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- oauth2 服务端,用于搭建授权服务器 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency><groupId>org.example</groupId><artifactId>common-mybatis</artifactId><version>${project.version}</version>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 引入JWT -->
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId>
</dependency>
2)SecurityConfiguration (继承WebSecurityConfigurerAdapter)是用于spring security的配置,主要是配置用户登录
3)JwtAuthenticationTokenFilter是结合spring security登录返回token方式
这两部分照搬了子模块auth-security,大家学习spring security也可以去参考该项目
4)AuthorizationServerConfiguration(继承AuthorizationServerConfigurerAdapter),这是OAuth2的关键配置,其中包括clientDetails(客户端注册)、JWT、授权模式等注册。
5)AccessTokenConfig是结合了JWT+RAS密钥方式,对token进行加密处理。资源服务器要判断客户端的token是否有效,一般使用授权服务器的check_token接口进行认证,但是还有一种方式就是通过RAS生成公钥和私钥,然后对token进行私钥加密,把公钥发给客户端,客户端通过公钥解密,这样就可以不用调用授权服务器也能对token有效性进行认证
6)本项目的客户端注册采用数据库存储,因此需要在数据库中创建以下表和数据:
create table oauth_client_details (client_id VARCHAR(256) PRIMARY KEY,resource_ids VARCHAR(256),client_secret VARCHAR(256),scope VARCHAR(256),authorized_grant_types VARCHAR(256),web_server_redirect_uri VARCHAR(256),authorities VARCHAR(256),access_token_validity INTEGER,refresh_token_validity INTEGER,additional_information VARCHAR(4096),autoapprove VARCHAR(256)
);INSERT INTO commonframework.oauth_client_details
(client_id, resource_ids, client_secret, `scope`, authorized_grant_types, web_server_redirect_uri, authorities, access_token_validity, refresh_token_validity, additional_information, autoapprove)
VALUES('client-lq', 'auth-resource1', 'secret-lq', 'all', 'authorization_code,refresh_token', 'https://www.baidu.com', NULL, 0, 0, NULL, 'true');
具体参考子模块auth-authentication的README.md
4 资源服务器-代码实践
在实际应用中,资源服务器和授权服务器一般都会分开,资源服务器通过客户端的请求带来的token,去授权服务器进行认证。在微服务的统一权限认证中,一般会通过网关gateway进行权限认证,那么结合OAuth2的话,gateway也会视为一个资源服务器(这一部分后面再讲)。这里的资源服务器通过配置授权服务器的check_token方式进行token认证。
参考子模块auth-resource
4.1 资源服务器的原理
通过访问远程授权服务器的 http://localhost:8080/oauth/check_token 接口获得权限。
有2种配备方式:
1)一种是采用yml+注解方式
2)一种是采用ResourceServerConfigurerAdapter方式
4.2 操作步骤(采用yml+注解方式)
1.引入相关的包
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- oauth2 服务端 -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
2.yml文件增加访问远程授权服务器的注解
security:oauth2:client:#基操client-id: client-lqclient-secret: secret-lqresource:#因为资源服务器得验证你的Token是否有访问此资源的权限以及用户信息,所以只需要一个验证地址token-info-uri: http://localhost:8080/oauth/check_tokenid: auth-resource1
3.在ResourceApplication中配置@EnableResourceServer注解
4.新建一个demo接口UserResourceController,如果在授权服务器配置了authorities,则可以在方法前面增加@PreAuthorize(“hasAuthority(‘read:user’)”)来控制方法级别的权限
4.3 权限配置说明
第1层资源级别:id: auth-resource1;与授权服务器中字段resource_ids配置相对应
第2层读写多端级别:scope=all,表示访问该资源服务器中的某些资源所需的权限;与授权服务器中的字段scope配置相对应
第3层方法级别:.antMatchers(“/product/*”).hasAuthority(“product”)或者@PreAuthorize(“hasAuthority(‘test’)”);表示具体某个接口所需的权限;与授权服务器的字段authorities相对应