WebSecurityConfigurerAdapter 中 httpBasic 和 jdbcAuthentication 的用户验证问题

Posted

技术标签:

【中文标题】WebSecurityConfigurerAdapter 中 httpBasic 和 jdbcAuthentication 的用户验证问题【英文标题】:Problem with user validation by httpBasic and jdbcAuthentication in WebSecurityConfigurerAdapter 【发布时间】:2020-01-13 11:42:50 【问题描述】:

我检查了 *** 中的大部分解决方案以及一些官方文章,例如:spring.io 我已经完成了基于 SpringBoot 的小型 webapi,我想从 inMemoryAuthentication 更改安全级别> 到 jdbcAuthentication,我认为我的代码是正确的,但我不知道为什么在 Postman 中仍然有 401 响应代码。

这是我的代码:

1.POM 依赖项

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
    域类 API 客户端
@Entity
@Table(name = "apiclient")
public class ApiClient 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @NotNull
    @Column(name = "username")
    @Size(max = 50)
    private String username;

    @NotNull
    @Column(name = "password")
    @Size(max = 65)
    private String password;

    @NotNull
    @Column(name = "role")
    @Size(max = 15)
    private String role;

    public Long getId() 
        return id;
    

    public void setId(Long id) 
        this.id = id;
    

    public String getUsername() 
        return username;
    

    public void setUsername(String username) 
        this.username = username;
    

    public String getPassword() 
        return password;
    

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

    public String getRole() 
        return role;
    

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

    @Override
    public String toString() 
        return "ApiClient" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", role='" + role + '\'' +
                '';
    

    休息控制器
@RestController
public class HelloController 

    @RequestMapping(path = "/user")
    public String showUserMsg() 
        return "User has logged in!!!";
    

    @RequestMapping(path = "/admin")
    public String showAdminMsg() 
        return "Admin has logged in!!!";
    

    安全等级
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.httpBasic();
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/h2-console/**").permitAll()
                .anyRequest().authenticated();

        http.csrf().ignoringAntMatchers("/h2-console/**");
        http.headers().frameOptions().disable();
    

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery("select username, password, true"
                        + " from apiclient where username='?'")
                .authoritiesByUsernameQuery("select username, role"
                        + " from apiclient where username='?'");
    

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

    自动用户注册的命令行运行器
public class SampleDataLoader implements CommandLineRunner 

    @PersistenceContext
    EntityManager entityManager;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    @Transactional
    public void run(String... args) throws Exception 

        ApiClient user = new ApiClient();
        user.setRole("USER");
        user.setPassword(passwordEncoder.encode("TESTPAS123"));
        user.setUsername("USER");
        entityManager.persist(user);
        entityManager.flush();

        System.out.println(user);

        ApiClient admin = new ApiClient();
        admin.setRole("ADMIN");
        admin.setPassword(passwordEncoder.encode("TESTPAS123"));
        admin.setUsername("ADMIN");
        entityManager.persist(admin);
        entityManager.flush();

        System.out.println(admin);
    

启动 springboot 应用程序 h2 数据库后如下所示:

【问题讨论】:

请检查您的用户名和角色查询,该查询应仅返回特定字段 您存储的密码加密对吗? 是的,就像你在 ss 上看到的那样 【参考方案1】:

使用单引号 username='?' 查询导致问题。

修改

 @Autowired
 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
 
     auth.jdbcAuthentication().dataSource(dataSource)
             .usersByUsernameQuery("select username, password, true"
                 + " from apiclient where username='?'")
         .authoritiesByUsernameQuery("select username, role"
                 + " from apiclient where username='?'");

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
    auth.jdbcAuthentication()
            .dataSource(dataSource)
            .usersByUsernameQuery("select username, password, true"
                    + " from apiclient where username=?")
            .authoritiesByUsernameQuery("select username, role"
                    + " from apiclient where username=?");

查看您从 github 链接共享的代码后, 在您的项目中,您尚未启用 DEBUG 级别的日志。

启用调试日志后我注意到

DEBUG - Executing prepared SQL statement [select username, password, true from apiclient where username='?'] 
DEBUG - Fetching JDBC Connection from DataSource 
...
DEBUG - Caching SQL error codes for DataSource [com.zaxxer.hikari.HikariDataSource@3bcd426c]: database product name is 'H2' 
DEBUG - Unable to translate SQLException with Error code '90008', will now try the fallback translator

...

DEBUG - Access is denied (user is anonymous); redirecting to authentication entry point 
org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:84)

您的查询未能获取 usersByUsername(因为查询中的单引号)并且由于异常未触发 authorityByUsername 查询,导致 spring-security 将用户视为 ROLE_ANONYMOUS 并因此 401(未授权)

如果您想在 STS/Eclipse 控制台中查看日志。 1.在src/main/resources下创建logback.xml文件 2.复制以下代码

<?xml version="1.0" encoding="UTF-8"?>

<configuration>
    <appender name="ALL_ROLLING_FILE"
        class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy
            class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>..\logs\SMTH_Project.%dyyyy-MM-dd_HH.%i.log.zip</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy
                class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>%dyyyy MM dd HH:mm:ss:SSS [%-40thread] %-5level5 - %msg %n</pattern>
        </encoder>
    </appender>

    <appender name="ASYNC"
        class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="ALL_ROLLING_FILE" />
        <queueSize>1000000</queueSize>
        <discardingThreshold>0</discardingThreshold>
    </appender>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%-5level5 - %msg %n</pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

【讨论】:

@Pawel 你试过了吗? 不,不是,我刚刚创建了带有文件的存储库github.com/PawelSzymanski89/BasicAuth @Pawel 更新了答案。

以上是关于WebSecurityConfigurerAdapter 中 httpBasic 和 jdbcAuthentication 的用户验证问题的主要内容,如果未能解决你的问题,请参考以下文章