gpt4 book ai didi

spring-boot - Spring Security with JWT tokens 的主要概念(Spring boot with REST controllers)

转载 作者:行者123 更新时间:2023-12-05 00:51:04 24 4
gpt4 key购买 nike

简介:
我刚开始使用 Spring Boot 。为了理解它是如何工作的,我尝试将我现有的项目(前端的 spring MVC、JSP)转换为在前端使用 REST Controller 和 AngularJS 的 spring boot 方法。

面临的问题:
在迁移过程中,我遇到了安全方面的大问题。据我了解,现在拥有良好安全层的最佳方法是使用 JWT token 并支持 oauth2,其中有很多帖子/教程提供了不同的信息,甚至是关于安全层架构的基础知识。

那么问题是:
有人可以指出安全层部件/类的完整列表,这些是为带有 REST Controller 的 spring boot 应用程序提供基本(但不是 hello world)安全功能所必需的。请不要建议使用stormpath : 我想自己实现它以获得更好的理解。

在这里问这个大问题的原因:
我已经对此主题进行了自己的调查,但我认为我检查过的大多数链接都包含很多不良做法,因此安全层的架构可能不正确。所以我真的很想知道一些设计安全层架构的良好实践。

所需功能的详细信息:
我有我想要支持的标准功能列表。

  • oauth2 支持(但也有可能在没有它的情况下进行身份验证)
  • 注册请求(创建 jwt token 并返回给客户端)
  • 登录请求(如果用户已注册则获取 jwt token )
  • 注销请求(释放 jwt token )
  • token 超时
  • 身兼数职
  • 用于检查身份验证和授权的业务休息 Controller (您能否提供部分代码示例)
  • 不需要安全的业务休息 Controller
  • 基本过滤 http url(比如从允许的 url 地址中排除“statics”)

项目的当前层:
以下是有关我当前项目结构的一些附加信息:目前我已经实现了以下模块:

  • controller: 目前是 MVC controller,但我要把它们转换成 REST
  • dto:可能会因为 REST 方法而改变一点
  • 模型:对话后保持不变
  • 异常(exception):对于业务逻辑
  • repository:对话后保持不变
  • 服务:可能会因为微服务而改变一点
  • validator:对话后保持不变
  • 其他业务逻辑模块

如果我理解正确,我需要在这里添加两个附加层:

  • 配置:我已经将一些 xml 配置器转换为 java-configs,但还没有触及安全配置器
  • security:我 guest 这里将放置身份验证/授权管理器/工具。这个问题的目标之一是了解究竟要放在这里什么。
  • 在根包(相对根)中具有 main 方法的应用类

最佳答案

您可以从创建 3 个项目开始。

  1. Auth Server:这将负责对客户端和用户进行身份验证、颁发 token 、撤销 token 等。
  2. Rest API:所有 rest Controller 、业务逻辑、持久层等。
  3. 前端:Angular JS、HTML、CSS 等

阅读有关 OAuth2 授权类型的信息。

我们使用 password授权服务器和客户端由同一组织开发时,或者资源所有者与客户端之间存在高度信任时的授权授予类型。

以下是 OAuth2 实现所需的基本类:

扩展 AuthorizationServerConfigurerAdapter 以配置授权服务器的类。在这里您可以配置端点,例如 userDetailsS​​ervice(自定义类以通过用户名从数据库加载用户数据)、tokenStore(将 token 存储在数据库中并对其执行操作)、clientDetailsS​​ervice(从数据库加载客户端详细信息;您的 Rest API 项目可以是客户端) .

@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager);
endpoints.userDetailsService(userDetailsService);
endpoints.tokenStore(tokenStore);
endpoints.setClientDetailsService(clientDetailsService);
endpoints.accessTokenConverter(accessTokenConverter);
}


@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
//The expression below easies access to /oauth/check_token endpoint from the default denyAll to isAuthenticated.
oauthServer.checkTokenAccess("isAuthenticated()");
oauthServer.allowFormAuthenticationForClients();
oauthServer.passwordEncoder(passwordEncoder);
}


@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetailsService);
}

一个扩展 ResourceServerConfigurerAdapter 的类。在这里您可以为资源服务器配置安全配置。资源将是 Auth 服务器中定义的 Rest Controller (例如用于对用户对象执行 CRUD 操作的 Controller 、撤销 token 的端点;需要在 Auth 服务器中的 Controller )。

@Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated(); //To restrict all http requests.
/*http.authorizeRequests().antMatchers("/users/**").permitAll(); //Notice ant matcher here, this tells endpoints which do not require authentication. Lots of http configuration options (like applying filters, cors, csrf etc.) are available here. Please explore*/
}

查看 TokenStore默认实现类(如 JdbcTokenStoreJwtTokenStore)。如果您想像 Cassandra 一样使用 NoSQL 数据库,请提供自定义 TokenStore 实现。

以下是用于 Cassandra 的自定义 token 存储的示例代码片段:

@Override
public void storeAccessToken(final OAuth2AccessToken token, final OAuth2Authentication authentication) {

String refreshToken = null;
if (token.getRefreshToken() != null) {
refreshToken = token.getRefreshToken().getValue();
}

if (readAccessToken(token.getValue()) != null) {
removeAccessToken(token.getValue());
}

final AccessTokenBuilder accessTokenBuilder = new AccessTokenBuilder();
accessTokenRepository.save(accessTokenBuilder
.withAuthenticationId(authenticationKeyGenerator.extractKey(authentication))
.withTokenId(extractTokenKey(token.getValue()))
.withTokenBody(ByteBuffer.wrap(serializeAccessToken(token)))
.withUsername(authentication.getName())
.withClientId(authentication.getOAuth2Request().getClientId())
.withAuthentication(ByteBuffer.wrap(serializeAuthentication(authentication)))
.withRefreshTokenId(extractTokenKey(refreshToken))
.build());

}


@Override
public void storeRefreshToken(final OAuth2RefreshToken refreshToken, final OAuth2Authentication authentication) {
final RefreshTokenBuilder refreshTokenBuilder = new RefreshTokenBuilder();
refreshTokenRepository.save(refreshTokenBuilder
.withTokenId(extractTokenKey(refreshToken.getValue()))
.withTokenBody(ByteBuffer.wrap(serializeRefreshToken(refreshToken)))
.withAuthentication(ByteBuffer.wrap(serializeAuthentication(authentication)))
.build());
}


@Override
public OAuth2Authentication readAuthentication(final OAuth2AccessToken token) {
return readAuthentication(token.getValue());
}


@Override
public OAuth2Authentication readAuthentication(final String token) {
OAuth2Authentication authentication = null;

try {
final AccessToken authAccessToken = accessTokenRepository.findByTokenId(extractTokenKey(token));
authentication = deserializeAuthentication(authAccessToken.getAuthentication().array());
} catch (final IllegalArgumentException e) {
removeAccessToken(token);
}

return authentication;
}


@Override
public OAuth2AccessToken readAccessToken(final String tokenValue) {
final AccessToken accessToken = accessTokenRepository.findByTokenId(extractTokenKey(tokenValue));
return accessToken != null ? deserializeAccessToken(accessToken.getTokenBody().array()) : null;
}

@Override
public OAuth2RefreshToken readRefreshToken(final String tokenValue) {
final RefreshToken refreshToken = refreshTokenRepository.findOne(extractTokenKey(tokenValue));
return refreshToken != null ? deserializeRefreshToken(refreshToken.getTokenBody().array()) : null;
}


@Override
public OAuth2Authentication readAuthenticationForRefreshToken(final OAuth2RefreshToken token) {
return readAuthenticationForRefreshToken(token.getValue());
}


OAuth2Authentication readAuthenticationForRefreshToken(final String tokenValue) {
final RefreshToken refreshToken = refreshTokenRepository.findOne(extractTokenKey(tokenValue));
return refreshToken != null ? deserializeAuthentication(refreshToken.getAuthentication().array()) : null;
}


@Override
public OAuth2AccessToken getAccessToken(final OAuth2Authentication authentication) {
OAuth2AccessToken oAuth2AccessToken = null;
final String key = authenticationKeyGenerator.extractKey(authentication);
final AccessToken accessToken = accessTokenRepository.findOne(key);
if (accessToken != null) {
oAuth2AccessToken = deserializeAccessToken(accessToken.getTokenBody().array());
if (oAuth2AccessToken != null && !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(oAuth2AccessToken.getValue())))) {
removeAccessToken(oAuth2AccessToken.getValue());
storeAccessToken(oAuth2AccessToken, authentication);
}
}

return oAuth2AccessToken;
}

您需要为数据库操作声明存储库接口(interface)。扩展 CrudRepository 的接口(interface)。对于大部分的DB操作我们不需要提供实现,由Spring来处理。 Cassandra 的实现在 SimpleCassandraRepository 类中。访问 token 的示例代码:

public interface AccessTokenRepository extends CrudRepository<AccessToken, String> {

@Query("SELECT * FROM auth_service.oauth_access_token WHERE token_id = :tokenId ALLOW FILTERING")
AccessToken findByTokenId(@Param("tokenId") String tokenId);
}

ClientDetails 的示例代码

public interface ClientDetailsRepository extends CrudRepository<ClientDetails, String> {

}

请注意,我们不需要为这些接口(interface)提供实现。常规的 CRUD 查询已经由 Spring 实现和处理。

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {

}

Rest API 项目

当从前端收到请求(来自 javascript 的 AJAX 请求)时,将调用此处声明的 Controller 。所有业务逻辑和持久层都将放在此处。

在这里你可以考虑创建一个模块,一个网关,它与 Auth Server 对话。此网关将位于您的 Rest API 和 Auth Server 之间。您可以使用 RestTemplate调用远程 Rest 服务。

如果您需要任何 Rest API 项目都不能远程调用 Auth Server,那么用户 client_credentials以及 password 授权类型。并且,使用 OAuth2RestTemplate而不是 RestTemplate。示例代码:

<bean id="oAuth2RestTemplate" class="org.springframework.security.oauth2.client.OAuth2RestTemplate">
<constructor-arg ref="clientCredentialsResourceDetails"/>
<constructor-arg ref="defaultOAuth2ClientContext"/>
<property name="requestFactory" ref="httpComponentsClientHttpRequestFactory"/>
</bean>

<bean id="httpComponentsClientHttpRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
<constructor-arg ref="selfSignedHttpsClientFactory"/>
</bean>

<bean id="clientCredentialsResourceDetails" class="org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails">
<property name="accessTokenUri" value="${authentication.service.client.token.url:https://localhost:8443/oauth/token}"/>
<property name="clientId" value="${authentication.service.client.id:testClient}"/>
<property name="clientSecret" value="${authentication.service.client.secret:password}"/>
</bean>

<bean id="defaultOAuth2ClientContext" class="org.springframework.security.oauth2.client.DefaultOAuth2ClientContext"/>

我希望这对您有所帮助。

关于spring-boot - Spring Security with JWT tokens 的主要概念(Spring boot with REST controllers),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44991536/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com