【问题标题】:Spring Security 401(Unauthorized) using UserDetailsService, jpaSpring Security 401(未授权)使用 UserDetailsS​​ervice,jpa
【发布时间】:2021-02-25 13:54:55
【问题描述】:

我有一个嵌入式 h2-databse,我在其中存储用户详细信息并尝试使用来自这些存储用户的数据授权请求,但 onlu permitAll() 请求正在工作。

安全配置类:错误很可能来自自动化配置。

package engine;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@ComponentScan(basePackages = {"engine"})
@EnableWebSecurity(debug = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
    private final APIUserDetailsService userDetailsService;

    @Autowired
    public SecurityConfiguration(APIUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) {
         http.csrf().disable()
                .httpBasic().and()
                .authorizeRequests()
                .antMatchers("/api/quizzes/**", "/api/quizzes").hasAuthority("ROLE_USER")
                .antMatchers("/", "/actuator/shutdown","/h2-console/**", "/api/register").permitAll()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and().headers().frameOptions().disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

实现 UserDetails 的类我硬编码了一些东西,在 db 中不需要。我还在下面附加了 User 类。

package engine;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

public class APIUserDetails implements UserDetails {

    private String userName;
    private String password;
    private List<GrantedAuthority> authorities;

    public APIUserDetails(User user) {
        this.userName = user.getEmail();
        this.password = user.getPassword();
        this.authorities = Arrays.asList(new SimpleGrantedAuthority(user.getRole()));
    }

    public APIUserDetails() {
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

用户详情服务

package engine;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
public class APIUserDetailsService implements UserDetailsService {

    private UserRepository userRepository;

    @Autowired
    public APIUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        Optional<User> user = userRepository.findByEmail(userName);
        user.orElseThrow(() -> new UsernameNotFoundException("Not Found: " + userName));
        return user.map(APIUserDetails::new).get();
    }
}

用户

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @Email(regexp = ".+@.+\\..+")
    @NotNull
    @Column(unique = true)
    private String email;

    @NotNull
    @Length(min = 5)
    private String password;

    @JsonIgnore
    private String role;

    @JsonIgnore
    @OneToMany(mappedBy = "user",
            cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    private List<Quiz> quizzes;

    public User() {
        this.quizzes = new ArrayList<>();
        this.role = "ROLE_USER";
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public List<Quiz> getQuizzes() {
        return quizzes;
    }

    public void setQuizzes(List<Quiz> quizzes) {
        this.quizzes = quizzes;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

用户存储库

package engine;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Integer> {
    Optional<User> findByEmail(String email);
}

关于使用 GET /api/quizzes


Request received for GET '/api/quizzes':

org.apache.catalina.connector.RequestFacade@309aa9c2

servletPath:/api/quizzes
pathInfo:null
headers: 
authorization: Basic dGVzdEBnb29nbGUuY29tOnF3ZXJ0eQ==
user-agent: PostmanRuntime/7.26.8
accept: */*
postman-token: 06cb7b25-176d-411c-9e8d-47b40e5a7820
host: localhost:8889
accept-encoding: gzip, deflate, br
connection: keep-alive


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  LogoutFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]


************************************************************


2020-11-14 01:58:42.790  WARN 6630 --- [nio-8889-exec-2] o.s.s.c.bcrypt.BCryptPasswordEncoder     : Encoded password does not look like BCrypt
2020-11-14 01:58:42.795  INFO 6630 --- [nio-8889-exec-2] Spring Security Debugger                 : 

************************************************************



Request received for GET '/error':

org.apache.catalina.core.ApplicationHttpRequest@54bf9f9a

servletPath:/error
pathInfo:null
headers: 
authorization: Basic dGVzdEBnb29nbGUuY29tOnF3ZXJ0eQ==
user-agent: PostmanRuntime/7.26.8
accept: */*
postman-token: 06cb7b25-176d-411c-9e8d-47b40e5a7820
host: localhost:8889
accept-encoding: gzip, deflate, br
connection: keep-alive


Security filter chain: [
  WebAsyncManagerIntegrationFilter
  SecurityContextPersistenceFilter
  HeaderWriterFilter
  LogoutFilter
  BasicAuthenticationFilter
  RequestCacheAwareFilter
  SecurityContextHolderAwareRequestFilter
  AnonymousAuthenticationFilter
  SessionManagementFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]

【问题讨论】:

  • 你有日志或者loadUserByUsername返回什么?
  • UserDetailsS​​ervice 接口需要loadByUsername() 方法,该方法返回一个实现UserDetails 的类。UserRepository 使用findByEmail() 获取用户详细信息,然后映射到userDetails 类。
  • 我看到您在 User 中有 ROLE_USER,但是 Spring Security 将使用 user.map(APIUserDetails::new).get(); 中的 UserDetails 来确定授权;你能发布这个逻辑吗?您是否确保 UserDetails 具有 ROLE_USER 的 GrantedAuthority?
  • @RobWinch 在APIUserDetails 类中,我使用覆盖函数getAuthorities() 指定授予权限,其中我将String USER_ROLE 转换为SimpleGrantedAuthority 列表
  • 您请求的 HTTP 方法/URL 失败了?您可以在提出请求时发布您的日志吗?

标签: java spring spring-boot hibernate spring-security


【解决方案1】:

由于您使用的是BCryptPasswordEncoder,因此请确保数据库中的密码已正确散列。要检查你应该确保密码以$2a开头。

注意删除以下代码是安全的,因为 Spring Security 自动使用暴露为 Bean 的 UserDetailsPasswordEncoder

private final APIUserDetailsService userDetailsService;

    @Autowired
    public SecurityConfiguration(APIUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

【讨论】:

    猜你喜欢
    • 2012-06-11
    • 2011-05-02
    • 1970-01-01
    • 1970-01-01
    • 2015-04-07
    • 1970-01-01
    • 2020-01-03
    • 2016-01-24
    • 2016-03-09
    相关资源
    最近更新 更多