本文来自于公众号链接: 彻底理解浏览器同源策略SOP
)
- 多种认证方式的优先级问题,如何杜绝冲突的问题
- 两个示例的描述不清晰的问题
这是一篇理论和实战相结合的干货文章,建议手机阅读者收藏本文,然后使用电脑下载源码进行实战
大纲:
- 一.概述
- 二.源码
- 三.示例1:接口分多组,每组认证方式不同
- 四.示例2:同组接口同时支持多种认证
- 五.总结
- 六.参考
一.概述
很多朋友虽然使用Spring Security做过很多项目,但是总是感觉没有理解和掌握Spring Security的核心思想。Spring Security比Shiro更强大更灵活的同时,确实带来了一定的复杂性。然而这种复杂性并不是因为Spring Security设计得不好造成的,而是因为安全需求本身就像一团乱麻一样错综复杂,Spring Security成功地将这团乱麻梳理柔顺后呈现给代价。在解决复杂安全问题场景下,Spring Security已经足够简洁了,我们更应该关注Spring Security的灵活性。
认证、鉴权和漏洞防御是安全框架的核心功能。Spring Security的认证功能强大并且非常灵活,支持的认证方式也在不断地水平扩展,已经预置了非常多的认证方式,如:
- Form认证
最常用的(用户名/密码)认证方式及其变体,如(手机号/验证码)
插图:用户名密码 - HTTP BASIC认证
插图:浏览器弹出框 - LDAP认证
大型环境如企业用户经常采用LDAP - CAS认证
单点登录场景下常用解决方案 - HTTP Digest 认证头 ( IETF RFC-based 标准)
- HTTP X.509 客户端证书交换 ( IETF RFC-based 标准)
- OpenID 认证
- …
如果预置的认证仍然不满足需求,Spring Security支持用户自定义认证方式。
除了认证方式的丰富性,Spring Security对认证的配置也非常灵活。Spring Security支持多种认证方式自由组合。
本文主要通过两个代码示例来展示Spring Security认证的灵活性:
- 第一个配置示例: 接口分多组,每组认证方式不同
- 第二个配置示例: 同组接口同时支持多种认证
二.源码
本文源码维护在Github,非常具有参考价值,建议下载源码:
- https://github.com/andyzhaozhao/spring-security-url-separate
有两个模块:
插图: twomodule
- spring-security-url-separate-sample: 本文展示Spring Security认证的灵活性的配置示例。
- authorizationserver:自建的OAuth2认证服务器。
三.示例1:接口分多组,每组认证方式不同
假设一个web应用的一部分接口是暴露在互联网上提供服务API接口,而另一部分接口是只需内部访问的内部接口。我们常常把这个web应用配置为OAuth2资源服务器,其中API接口采用OAuth2资源服务欧认证,内部接口则采用传统的认证方式如Form认证。
Spring Security支持这种“接口分多组,每组认证方式不同”类型的需求。
1.新建Spring Boot工程
新建Spring Boot工程,命名为“spring-security-url-separate-sample”,依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring-boot-starter-oauth2-resource-server是OAuth2资源服务器功能包。
2.增加两个Controller
FooController:
@RestController
public class FooController {
@GetMapping("/foo")
public String getSample() {
return "get foo";
}
}
和BarController:
@RestController
public class BarController {
@GetMapping("/bar")
public String getSample() {
return "get bar";
}
}
两个Controller用来模拟两组API接口。
3.两个Spring Security配置类
GroupOneSecurityConfig配置所有以“/foo”开头的接口使用OAuth2资源服务进行认证和鉴权:
@Configuration
//此处配置Order=1,因而比 GroupTwoSecurityConfig 配置类先执行,配置优先级高,因为Spring Security的配置规则是"先执行的优先级高"。
@Order(1)
public class GroupOneSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/foo/**")
.and().authorizeRequests().anyRequest().fullyAuthenticated()
.and().oauth2ResourceServer().jwt();
}
}
GroupTwoSecurityConfig配置除了以“/foo”开头的接口外剩余的其他接口使用From认证:
@EnableWebSecurity(debug = true)
public class GroupTwoSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/error", "/login**").permitAll()
.anyRequest().fullyAuthenticated()
.and().formLogin();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password(passwordEncoder().encode("123456"))
.roles("ADMIN", "USER");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
GroupTwoSecurityConfig类配置的接口选择范围为“所有接口”,包含了GroupOneSecurityConfig选择的以“/foo”开头的接口,但是不用担心两个配置类的范围冲突问题,因为我们在 GroupOneSecurityConfig上增加了@Order(1)注解。@Order表示执行优先级,WebSecurityConfigurerAdapter默认的优先级是100:
插图:order
Spring Security的@Order规则是:“数字越小,越先执行,并且先执行的配置优先级高”。
@Order(1)小于默认的@Order(100),所以GroupOneSecurityConfig的接口选择“/foo”的优先级高、先生效,而GroupTwoSecurityConfig只能选择到除“/foo”以外剩余的接口。
工程的接口分为了两组:
- 以“/foo”开头的接口,使用OAuth2资源服务认证
- 除“/foo”开头接口外剩余的接口,使用From认证。
注意GroupTwoSecurityConfig要配置“/error”和“/login**”为permitAll()
4.配置文件
server:
servlet:
session:
cookie:
name: UISESSIONCOUPON
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:9999/.well-known/jwks.json
自建OAuth2认证服务器和本工程都以localhost启动,配置不同的cookie名称,防止冲突报错,并且使用JWK接口验证 JWT Token 的有效性。
5.运行与演示
假设spring-security-url-separate-sample是一个在互联网提供API接口的服务器,其中“/foo”接口暴露在互联网提供服务的API接口,而 “/bar”接口为内部接口,则有如下访问场景:
- 内部接口可以直接使用Form认证:
- 启动“spring-security-url-separate-sample”
- 隐身模式打开浏览器,输入:http://localhost:8080/bar
插图:formlogin - 输入用户名/密码:admin/123456,可以登录成功
- 浏览器再输入:http://localhost:8080/foo ,也可以成功返回字符串“get foo”。
用户使用Form登录后既可以访问API接口也可以访问内部接口。
- 暴露在互联网的API接口只能使用OAuth2资源服务认证:随便来自于互联网的用户不能获得内部的用户名和密码
-
启动“spring-security-url-separate-sample”
-
启动“sauthorizationserver”
-
隐身模式打开浏览器,输入:http://localhost:8080/foo
插图:foo401
未认证时,OAuth2资源服务认证不会自动重定向到认证中心,而是显示要401错误。调用
“/foo”接口是需要携带OAuth2的access token的。 -
使用curl命令行工具模拟OAuth2请求,使用密码模式获取一个access token:
curl -i -X POST -d "username=admin&password=admin&grant_type=password&client_id=client-for-server&client_secret=client-for-server" http://localhost:9999/oauth/token
插图:gettoken
-
携带access_token请求“/foo”接口:
curl -i -H "Authorization:Bearer {此处粘贴上一步骤返回的access_token}" http://localhost:8080/foo
插图:foosuccess
外部用户成功调用“/foo”接口,但是仍然无法调用到内部“/bar”接口。内部接口”被成功地保护起来了。
-
此示例将应用接口分为两组,分别使用不同的认证方式。理论上Spring Security可以将接口分为任意多组,使用任意多种认证方式,非常灵活。
四.示例2:同组接口同时支持多种认证
除了多组接口使用不同的认证方式外。对于同一组接口,可以同时支持多种认证方式。
我们继续对“spring-security-url-separate-sample”进行扩展。
1.增加OAuth2 Client依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
2.在GroupTwoSecurityConfig中增加配置
@Override
public void configure(HttpSecurity http) throws Exception {
...
.and().formLogin()
.and().oauth2Login();
}
对于除“/foo"以外的其他接口同时支持Form认证和OAuth2客户端认证。
3.配置文件增加配置
spring:
security:
oauth2:
client:
registration:
github:
client-id: b7fb29a538bb19b09365
client-secret: 2fbfd22e69e61d873bad55143538770748a76d3a
custom:
client-id: client-for-server
client-secret: client-for-server
provider: custom
client-name: 自建OAuth2认证服务
authorization-grant-type: authorization_code
redirect-uri: "http://localhost:8080/login/oauth2/code/custom"
provider:
custom:
authorization-uri: http://localhost:9999/oauth/authorize
token-uri: http://localhost:9999/oauth/token
user-info-uri: http://localhost:9999/me
user-name-attribute: "name"
jwk-set-uri: http://localhost:9999/.well-known/jwks.json
配置两个OAuth2客户端,一个是Github的OAuth2的客户端,另一是自建OAuth2认证服务器的客户端。
4.运行与演示
对于“/bar”接口,同时支持Form认证和OAuth2客户端认证。
- 启动“spring-security-url-separate-sample”
- 启动“sauthorizationserver”
- 隐身模式打开浏览器,输入:http://localhost:8080/bar
插图:oauth2login
此时我们有三种选择:- 输入用户名/密码:admin/123456登录成功。
- 点击“Github”蓝色按钮,使用Github账号登录成功。
- 点击“自建OAuth2认证服务”蓝色按钮,使用自建OAuth2认证服务,输入用户名/密码;admin/admin,登录成功。
插图: customas
此示例演示了Spring Security支持同组接口同时支持多种认证。
五.总结
spring-security-url-separate-sample整体来看同时支持了Form、GitHub认证、自建OAuth2认证服务客户端认证和自建OAuth2认证服务资源服务认证四种认证方式,并且不互相冲突。每种认证方式都有各自的应用场景。
Spring Security实现之所以可以实现如此灵活的认证配置,正是因为“按需装配”这一核心思想。
首先无论Spring Security应用在Servlet还是响应式(Reactive)场景,都是一种AOP切面原理。
插图:authenticaiton
其次一个web应用根据需求,需要使用哪种认证方式就装配哪种。
插图;asneed
总之,Spring Security除了支持漏洞防御,针对认证和鉴权的需求做了的非常强大且灵活的设计。
本文的相关源码上传到了Github,地址:https://github.com/andyzhaozhao/spring-security-url-separate
如果有任何问题和建议,可以右下角点赞后评论,我们会第一时间回复。
六.参考
更多干货都在《spring security实战》
本为官方出处微信公众号: 码闻