Use*_*r69 4 java spring-security jwt spring-boot
我正在尝试将更新的 Spring Security 集成到我的项目中,而不是使用已弃用的extending WebSecurityConfigurerAdapter. 我已经建立了一个很好的系统,用户可以在其中进行身份验证(User实现UserDetails- 我正在使用 Hibernate)并生成令牌。我在这次登录中得到了 200 并收到了一个令牌。这个认证部分工作正常。
现在的问题是我的用户具有角色(例如ADMIN,,USER...)这些角色被添加到生成的令牌中。我的控制器获得@PreAuthorize注释。请求无法通过这些注释并受到禁止。当我不使用 时@PreAuthorize,请求将使用令牌进行验证。
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private RSAKey rsaKey;
private final DefaultUserDetailsService defaultUserDetailsService;
public SecurityConfig(DefaultUserDetailsService defaultUserDetailsService) {
this.defaultUserDetailsService = defaultUserDetailsService;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.userDetailsService(defaultUserDetailsService)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.headers(headers -> headers
.frameOptions().sameOrigin()
)
.httpBasic(withDefaults())
.build();
}
@Bean
public JWKSource<SecurityContext> jwkSource() {
rsaKey = Jwks.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
JwtDecoder jwtDecoder() throws JOSEException {
return NimbusJwtDecoder.withPublicKey(rsaKey.toRSAPublicKey()).build();
}
@Bean
JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwks) {
return new NimbusJwtEncoder(jwks);
}
@Bean
public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:4200"));
configuration.setAllowedMethods(List.of("GET","POST","DELETE"));
configuration.setAllowedHeaders(List.of("Authorization","Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**",configuration);
return source;
}
}
Run Code Online (Sandbox Code Playgroud)
@Component
public 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;
}
}
Run Code Online (Sandbox Code Playgroud)
public 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();
}
}
Run Code Online (Sandbox Code Playgroud)
@Service
public class DefaultTokenService implements TokenService {
private final JwtEncoder encoder;
public DefaultTokenService(JwtEncoder encoder) {
this.encoder = encoder;
}
@Override
public String generateToken(Authentication authentication) {
Instant now = Instant.now();
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "));
System.out.println("scope: " + scope);
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now)
.expiresAt(now.plus(1, ChronoUnit.HOURS))
.subject(authentication.getName())
.claim("scope", scope)
.build();
return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
}
Run Code Online (Sandbox Code Playgroud)
public class UserDetailsImpl implements UserDetails{
private static final long serialVersionUID = 1L;
private final Long id;
private final String username;
private final String riziv;
private final boolean verified;
@JsonIgnore
private final String password;
private final Collection<? extends GrantedAuthority> authorities;
public UserDetailsImpl(Long id, String username, String riziv, String password,
Collection<? extends GrantedAuthority> authorities, boolean verified) {
this.id = id;
this.username = username;
this.riziv = riziv;
this.password = password;
this.authorities = authorities;
this.verified = verified;
}
public static UserDetailsImpl build(AuthUser authUser) {
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(authUser.getRol().toString()));
return new UserDetailsImpl(
authUser.getId(),
authUser.getUsername(),
authUser.getRiziv(),
authUser.getPassword(),
authorities, authUser.isVerified());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
public Long getId() {
return id;
}
public boolean isVerified() {
return verified;
}
public String getRiziv() {
return riziv;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
UserDetailsImpl klant = (UserDetailsImpl) o;
return Objects.equals(id, klant.id);
}
}
Run Code Online (Sandbox Code Playgroud)
@Service
public class DefaultUserDetailsService implements UserDetailsService {
private final AuthUserService authUserService;
public DefaultUserDetailsService(AuthUserService authUserService) {
this.authUserService = authUserService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AuthUser authUser = authUserService.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
return UserDetailsImpl.build(authUser);
}
}
Run Code Online (Sandbox Code Playgroud)
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
Run Code Online (Sandbox Code Playgroud)
我在某处犯了配置错误,但我似乎找不到它。Spring 文档非常非常难以理解,但我一直在坚持不懈地阅读它们。关于这些主题还没有很多明确的信息。我可以找到 youtube 视频教程和一些相关主题,但它们只解释小部分,而不是完整的设置。
我在下面添加了我的securityConfig、KeyGenerator和Jwks服务tokengenerate。我还刚刚添加了Userdetailsimpl和 服务。我userdetailsImpl使用静态构建方法构建了用户。这看起来可能是一个奇怪的结构,但它确实有效,因为我是最后做安全的,之前没有想到这一点。我还添加了我的一个例子@Preauthorize。
我非常接近,这对于尝试实现此功能的其他用户来说可能是一个很好的例子,因为我似乎无法在某处找到示例。有人有设置 Spring Boot 3 安全性的经验吗?他们可以告诉我如何设置我配置错误吗?为什么我的角色没有被“读取” @PreAuthorize?
好吧,事情是这样的,因为您正在实现资源服务器,所以类:
org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
Run Code Online (Sandbox Code Playgroud)
负责将 JWT 令牌内的范围转换为授予的权限。现在,此类在所有权限前面加上SCOPE_前缀。
既然你正在使用
org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
Run Code Online (Sandbox Code Playgroud)
该方法内部调用,
hasAnyRole('ADMIN', 'USER',...)
Run Code Online (Sandbox Code Playgroud)
方法,其中包含defaultRolePrefixasROLE_和 the roleNameas 您传入的值。
内部实现:
hasAnyAuthorityName(defaultRolePrefix, roleName)
Run Code Online (Sandbox Code Playgroud)
另一方面,该hasAnyAuthority方法调用相同的方法,但null传递给defaultRolePrefix.
现在既然你正在使用:
@Override
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(this.defaultRolePrefix, roles);
}
@Override
public final boolean hasAnyAuthority(String... authorities) {
return hasAnyAuthorityName(null, authorities);
}
Run Code Online (Sandbox Code Playgroud)
在安全配置中,它使用AuthenticationConverterJWT 令牌的默认值,即
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
Run Code Online (Sandbox Code Playgroud)
这进一步调用了
org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
Run Code Online (Sandbox Code Playgroud)
根据 中的实现JwtGrantedAuthoritiesConverter,JWT 令牌中的所有范围都以SCOPE_我前面提到的前缀。
现在假设您授予的权限ADMIN作为角色之一返回。将其添加到scopeJWT 中后,默认转换器将SCOPE_ADMIN作为权限返回,同样,如果您在范围内返回,它将默认ROLE_ADMIN转换为。SCOPE_ROLE_ADMIN
该类JwtAuthenticationConverter返回一个实例
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
Run Code Online (Sandbox Code Playgroud)
因此,可以通过以下方式修复:
或者,通过附加到您在范围中设置的角色名称hasAnyAuthority来检查权限。SCOPE_如果您的角色名称是ADMIN或者ROLE_ADMIN您应该使用
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
Run Code Online (Sandbox Code Playgroud)
等等。如果你想使用hasAnyRole检查那么你必须使用
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter
Run Code Online (Sandbox Code Playgroud)
分别为ADMIN和ROLE_ADMIN值。
或者,实现自定义权限转换器并将其传递到oauth2ResourceServer安全配置,如下所示,
自定义转换器示例
org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
Run Code Online (Sandbox Code Playgroud)
然后将 Spring Security 配置更新为:
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
Run Code Online (Sandbox Code Playgroud)
使用第二个选项,您可以按照您想要的方式在 JWT 令牌和
@PreAuthorize("hasAnyAuthority('SCOPE_ADMIN')")
@PreAuthorize("hasAnyAuthority('SCOPE_ROLE_ADMIN')")
Run Code Online (Sandbox Code Playgroud)
检查应该获取ROLE_ADMIN范围ADMIN,而不是ROLE_SCOPE_ADMIN现在的情况。
| 归档时间: |
|
| 查看次数: |
4676 次 |
| 最近记录: |