本文来自于公众号链接: 两个示例来感受Spring Security认证的灵活性
公众号中文章格式更加良好,干货文章都在:码闻
这是一篇理论和实战相结合的干货文章,建议手机阅读者收藏,并且使用电脑下载源码进行实战
大纲:
一.概述
二.源码
三.示例1:接口分多组,每组认证方式不同
四.示例2:同组接口同时支持多种认证
五.总结
六.参考
一.概述
很多朋友虽然使用Spring Security做过很多项目,但是总是感觉没有理解和掌握Spring Security的核心思想。Spring Security比Shiro更强大更灵活的同时,确实带来了一定的复杂性。然而这种复杂性并不是因为Spring Security设计得不好造成的,而是因为安全需求本身就像一团乱麻一样错综复杂,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
有两个模块:
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”,依赖:
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:
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”接口为内部接口,则有如下访问场景:
1.内部接口可以直接使用Form认证:
启动“spring-security-url-separate-sample”
隐身模式打开浏览器,输入:http://localhost:8080/bar
输入用户名/密码:admin/123456,可以登录成功
浏览器再输入:http://localhost:8080/foo ,也可以成功返回字符串“get foo”。 用户使用Form登录后既可以访问API接口也可以访问内部接口。
2.暴露在互联网的API接口只能使用OAuth2资源服务认证
因为随便来自于互联网的用户不能获得内部的用户名和密码,但是可以获得OAuth2统一认证服务的用户名和密码。
启动“authorizationserver”
启动“spring-security-url-separate-sample”
隐身模式打开浏览器,输入:http://localhost:8080/foo
未认证时,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
携带access_token请求“/foo”接口:
curl -i -H"Authorization:Bearer {此处粘贴上一步骤返回的access_token}"http://localhost:8080/foo
外部用户成功调用“/foo”接口,但是仍然无法调用到内部“/bar”接口。内部接口”被成功地保护起来了。
此示例将应用接口分为两组,分别使用不同的认证方式。理论上Spring Security可以将接口分为任意多组,使用任意多种认证方式,非常灵活。
四.示例2:同组接口同时支持多种认证
除了多组接口使用不同的认证方式外。对于同一组接口,可以同时支持多种认证方式。
我们继续对“spring-security-url-separate-sample”进行扩展。
1.增加OAuth2 Client依赖
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
此时我们有三种选择:
输入用户名/密码:admin/123456登录成功。
点击“Github”蓝色按钮,使用Github账号登录成功。
点击“自建OAuth2认证服务”蓝色按钮,使用自建OAuth2认证服务,输入用户名/密码;admin/admin,登录成功。
此示例演示了Spring Security的同组接口同时支持多种认证。
五.总结
spring-security-url-separate-sample整体来看同时支持了Form、GitHub认证、自建OAuth2认证服务客户端认证和自建OAuth2认证服务资源服务认证四种认证方式,并且不冲突。每种认证方式都有各自的应用场景。需要注意多种认证组合时是可能产生冲突的。
Spring Security之所以可以实现如此灵活的认证配置,正是因为“按需装配”这一核心思想。首先无论Spring Security应用在Servlet还是响应式(Reactive)场景,都是一种AOP切面原理:
其次,一个web应用可以根据需求,需要使用哪种认证方式就装配哪种:
总之,Spring Security除了支持漏洞防御,Spring Security还针对认证和鉴权的需求做了非常强大且灵活的设计。
本文的相关源码上传到了Github,地址:https://github.com/andyzhaozhao/spring-security-url-separate
如果有任何问题和建议,可以右下角点赞后评论,我们会第一时间回复。
六.参考
更多干货都在《spring security实战》