OAuth 2.0 是行业标准的授权协议。OAuth 2.0 专注于客户端开发人员的简单性,同时为 Web 应用程序、桌面应用程序、移动电话和客厅设备提供特定的授权流程。
OAuth 授权服务器负责对用户进行身份验证并发布包含用户数据和适当访问策略的访问令牌。
下面我们将使用Spring Authorization Server构建一个简单的授权服务器。
让我们从OAuth2授权服务器配置实现开始。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.7</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.3.1</version>
</dependency>
首先让我们通过application.yml配置数据库连接信息.
spring:
application:
name: auth-server
datasource:
druid:
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/integrated_oauth?createDatabaseIfNotExist=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: <<username>> # 修改用户名
password: <<password>> # 修改密码
之后我们创建一个AuthorizationServerConfig配置类,在此类中我们将创建OAuth2授权服务器所需特定的Bean。第一个将是客户端服务存储库,我们使用RegisteredClient构建器类型创建一个客户端,并将它持久化到数据库中。
@Bean
public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("relive-client")
.clientSecret("{noop}relive-client")
.clientAuthenticationMethods(s -> {
s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
})
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.redirectUri("http://127.0.0.1:8070/login/oauth2/code/messaging-client-authorization-code")
.scope("message.read")
.clientSettings(ClientSettings.builder()
.requireAuthorizationConsent(true)
.requireProofKey(false)
.build())
.tokenSettings(TokenSettings.builder()
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
.accessTokenTimeToLive(Duration.ofSeconds(30 * 60))
.refreshTokenTimeToLive(Duration.ofSeconds(60 * 60))
.reuseRefreshTokens(true)
.build())
.build();
JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);
registeredClientRepository.save(registeredClient);
return registeredClientRepository;
}
我们配置的属性是:
id–Registeredclient唯一标识
clentId–客户端标识符
clientSecret–客户端秘密
clientAuthenticationMethods–客户端可能使用的身份验证方法。支持的值为client_secret_basic、client_secret_post、private_key_jwt、client_secret_jwt和none
authorizationGrantTypes–客户端可以使用的授权类型。支持的值为authorization_code、client_credentials和refresh_token
redirectUris–客户端已注册重定向 URI
scopes–允许客户端请求的范围。
clientSettings–客户端的自定义设置
tokenSettings–发布给客户端的 OAuth2 令牌的自定义设置
接下来让我们配置存储新授权和查询现有授权的中心组件OAuth2AuthorizationService。
@Bean
public OAuth2AuthorizationService authorizationService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
}
针对OAuth2 授权请求的授权“同意”,Spring提供了OAuth2AuthorizationConsentService存储新授权同意和查询现有授权同意的组件。
@Bean
public OAuth2AuthorizationConsentService authorizationConsentService(JdbcTemplate jdbcTemplate, RegisteredClientRepository registeredClientRepository) {
return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
}
接下来让我们创建一个bean,配置OAuth2授权服务其他默认配置,并以用来对与未认证的授权请求,将该请求重定向到登录页面。
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.exceptionHandling(exceptions -> exceptions.
authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))).build();
}
每个授权服务器都需要用于令牌的签名密钥,让我们生成一个RSA密钥:
final class KeyGeneratorUtils {
private KeyGeneratorUtils() {
}
static KeyPair generateRsaKey() {
KeyPair keyPair;
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
keyPair = keyPairGenerator.generateKeyPair();
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
return keyPair;
}
}
public final class Jwks {
private Jwks() {
}
public static RSAKey generateRsa() {
KeyPair keyPair = KeyGeneratorUtils.generateRsaKey();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
return new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID(UUID.randomUUID().toString())
.build();
}
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
处理完令牌的签名密钥,授权服务器还需要一个颁发者URL,我们可以通过ProviderSettings创建:
@Bean
public ProviderSettings providerSettings() {
return ProviderSettings.builder()
.issuer("http://127.0.0.1:8080")
.build();
}
最后我们将启用Spring Security安全配置类,以保护我们的服务。
@EnableWebSecurity
@Configuration
public class DefaultSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults())
return http.build();
}
//...
}
这里authorizeRequests.anyRequest().authenticated()让所有请求都需要认证,并提供基于Form表单的身份认证。
我们还需要定义测试使用的用户信息,以下创建一个基于内存的用户信息存储库。
@Bean
UserDetailsService users() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
现在我们将创建一个资源服务器,服务中的API接口只允许通过OAuth2授权服务器身份验证的请求。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
<version>2.6.7</version>
</dependency>
首先让我们通过application.yml配置服务端口。
server:
port: 8090
接下来,进行OAuth2安全配置,我们需要使用之前授权服务器在ProviderSettings中设置的issuerUri。
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://127.0.0.1:8080
资源服务器将使用此Uri进一步进行自我配置,发现授权服务器的公钥,并传入用于验证JWT的JwtDecoder。此过程的结果是授权服务器必须启动并接收请求才能使资源服务器成功启动。
如果资源服务器必须能够独立于授权服务器启动,那么可以提供jwk-set-uri。这将是我们进一步在OAuth2安全配置中添加属性:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://127.0.0.1:8080
jwk-set-uri: http://127.0.0.1:8080/oauth2/jwks
现在我们可以设置Spring Security安全配置,对服务资源的每个请求都应该被授权并具有适当的权限:
@EnableWebSecurity
public class ResourceServerConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/resource/test/**")
.and()
.authorizeRequests()
.mvcMatchers("/resource/test/**")
.access("hasAuthority('SCOPE_message.read')")
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
最后,我们将创建一个 REST 控制器,它将返回jwt的claims信息。
@RestController
public class ResourceServerTestController {
@GetMapping("/resource/test")
public Map<String, Object> getArticles(@AuthenticationPrincipal Jwt jwt) {
return jwt.getClaims();
}
}
现在我们要创建一个客户端,它首先向授权服务器请求授权获取访问令牌,在访问资源服务器对应资源。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>2.6.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webflux</artifactId>
<version>5.3.9</version>
</dependency>
首先我们将要在application.yml中配置客户端的访问端口8070。
server:
port: 8070
接下来我们将定义OAuth2客户端的配置属性:
spring:
security:
oauth2:
client:
registration:
messaging-client-authorization-code:
provider: client-provider
client-id: relive-client
client-secret: relive-client
authorization-grant-type: authorization_code
redirect-uri: "http://127.0.0.1:8070/login/oauth2/code/{registrationId}"
scope: message.read
client-name: messaging-client-authorization-code
provider:
client-provider:
authorization-uri: http://127.0.0.1:8080/oauth2/authorize
token-uri: http://127.0.0.1:8080/oauth2/token
现在让我们创建一个WebClient实例用于向资源服务器执行HTTP请求:
@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
return WebClient.builder()
.apply(oauth2Client.oauth2Configuration())
.build();
}
WebClient添加了一个OAuth2授权过滤器,需要OAuth2AuthorizedClientManager作为依赖项,在此处只配置了授权码和刷新token,如有需要可以添加其他模式:
@Bean
OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
//可以扩展其他模式
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder
.builder()
.authorizationCode()
.refreshToken()
.build();
DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
return authorizedClientManager;
}
最后,我们将配置Spring Security安全配置:
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorizeRequests ->
//便于测试,将权限开放
authorizeRequests.anyRequest().permitAll()
)
.oauth2Client(withDefaults());
return http.build();
}
这里我们将客户端API权限全部放开,但是在实际情况下,客户端服务是需要认证的,OAuth2协议本身是授权协议,并不关心认证的具体表现形式,你也可以向授权服务器一样添加简单的表单认证。
最后,我们创建一个控制器,我们将使用之前配置的WebClient向我们资源服务器发起HTTP请求:
@RestController
public class ClientTestController {
@Autowired
private WebClient webClient;
@GetMapping(value = "/client/test")
public Map<String, Object> getArticles(@RegisteredOAuth2AuthorizedClient("messaging-client-authorization-code") OAuth2AuthorizedClient authorizedClient) {
return this.webClient
.get()
.uri("http://127.0.0.1:8090/resource/test")
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(Map.class)
.block();
}
}
在上面示例中,我们使用@RegisteredOAuth2AuthorizedClient注解绑定OAuth2AuthorizedClient,并以触发OAuth2授权码模式流程获取访问令牌。
本例主要演示在使用OAuth2进行两个服务安全通信,尤其是在复杂的互联网场景中,客户端服务和资源服务恰好由不同的平台提供,OAuth2非常善于获取用户的委托决策,很多方面他都比其他方案更简单,更安全。
与往常一样,本文中使用的源代码可在 GitHub 上获得。
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我在开发的Rails3网站的一些搜索功能上遇到了一个小问题。我有一个简单的Post模型,如下所示:classPost我正在使用acts_as_taggable_on来更轻松地向我的帖子添加标签。当我有一个标记为“rails”的帖子并执行以下操作时,一切正常:@posts=Post.tagged_with("rails")问题是,我还想搜索帖子的标题。当我有一篇标题为“Helloworld”并标记为“rails”的帖子时,我希望能够通过搜索“hello”或“rails”来找到这篇帖子。因此,我希望标题列的LIKE语句与acts_as_taggable_on提供的tagged_with方法
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h