用户权限认证这一块一直是自己的一个盲点,之前的web demo都是通过用户名密码匹配做简单的登录认证。

最近觉得应该去了解规范的用户及权限认证技术了,从Spring Security开始学习使用。

本文记录学习Spring Security过程中遇到的一些问题及解决方案。

1. 版本问题

Spring Boot 2.x和Spring Security 5.x前配置用户名密码:

security.user.name=admin
security.user.password=admin

Spring Boot 2.x和Spring Security 5.x前禁用认证:

security.basic.enabled=false
management.security.enabled=false

Spring Boot 2.x和Spring Security 5.x后配置用户名密码:

spring.security.user.name=admin
spring.security.user.password=admin

Spring Boot 2.x和Spring Security 5.x后禁用认证:

Spring Boot 2.x和Spring Security 5.x后以下配置均失效

security.basic.authorize-mode
security.basic.enabled
security.basic.path
security.basic.realm
security.enable-csrf
security.headers.cache
security.headers.content-security-policyjun
security.headers.content-security-policy-mode
security.headers.content-type
security.headers.frame
security.headers.hsts
security.headers.xss
security.ignored
security.require-ssl
security.sessions

可以使用如下方式配置禁止认证:

@SpringBootApplication(exclude = {SecurityAutoConfiguration.class, 
        ManagementWebSecurityAutoConfiguration.class})
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }
}

 也可以使用:

@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class,args);
    }
}

2. 配置用户名密码及角色

配置文件配置如上;

java代码配置:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    /**
     * 配置请求相关
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("HttpSecurity");
        http.authorizeRequests()
                // 需要的权限为 ROLE_USER,没有显式的表达出来
                .antMatchers("/product/**").hasRole("USER") // 匹配某些请求路径,所需要的角色,配置角色权限
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated().and() // 所有请求认证
                .formLogin().and().httpBasic();
    }


    /**
     * 配置用户角色
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * 创建某些用户,给这些用户赋予权限
         */
        auth.inMemoryAuthentication()
                .withUser("admin1")
                .password("admin1")
                .roles("ADMIN","USER")
                .and()
                .withUser("user1")
                .password("user1")
                .roles("USER");
    }
}

3. 来用数据源的用户信息及认证

将用户信息存于数据库中,配置数据源。

核心在于实现UserDetailsService接口,并实现UserDetails loadUserByUsername(String login) throws UsernameNotFoundException方法;

这个方法中的核心逻辑为根据login在数据库中查询用户信息,并组装成

org.springframework.security.core.userdetails.User(login,
        userFromDatabase.getPassword(), grantedAuthorities);

此User为UserDetails的子类。

package cn.wqz.study.springboot.security.service.impl;

import cn.wqz.study.springboot.security.dao.UserRepository;
import cn.wqz.study.springboot.security.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;

import java.util.ArrayList;
import java.util.Collection;

/**
 * 从数据库中读取用户信息
 * 根据用户信息组装成security框架所需要的用户格式
 * 如封装权限,封装password等
 */
@Component("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
        System.out.println("user:" + login);
        // 1. 查询用户
        User userFromDatabase = userRepository.findOneByLogin(login);
        if (userFromDatabase == null) {
            //log.warn("User: {} not found", login);
            throw new UsernameNotFoundException("User " + login + " was not found in db");
            //这里找不到必须抛异常
        }
        // 2. 设置角色
        Collection<GrantedAuthority> grantedAuthorities = new ArrayList();
        GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(userFromDatabase.getRole());
        grantedAuthorities.add(grantedAuthority);

        // 3. 创建一个User, 其是UserDetails的子类
        return new org.springframework.security.core.userdetails.User(login,
                userFromDatabase.getPassword(), grantedAuthorities);
    }
}

然后在 WebSecurityConfigurerAdapter 中配置 UserDetailsService 服务:

/**
     * 配置用户角色
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        // 设置自定义的UserDetailsService,从库中获取标准的用户信息,并进行配置,发挥作用
        System.out.println("AuthenticationManagerBuilder");
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    }

    /**
     * 用户密码使用加密机制保护
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

其中BCryptPasswordEncoder负责用户密码的加密,在数据库中存储的密码为原密码经过BCryptPasswordEncoder加密后的密码密文。

当请求传来密码时与数据库中的密文经过BCryptPasswordEncoder处理后完成比较认证。

4. 关于Security请求 403

集成 Spring Security,成功登录后,访问拥有权限的页面,仍然报403错误

通过th:action="@{/api/user}" 或<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>处理:

<!DOCTYPE html>
<html lang="en"
        xmlns:th="http://thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>index</title>
</head>
<body>
<!-- @{/api/user} 解决post请求 403的问题-->
    <form th:action="@{/api/user}" method="post">
        <input name="id" type="text"/>
        <input name="login" type="text"/>
        <input name="password" type="password"/>
        <input name="role" type="text"/>
        <input type="submit"/>
    </form>


    <form action="/api/hello" method="post">
        name:<input type="text" name="name" id="name"/>
        <!-- 解决post请求 403的问题-->
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
        <input type="submit"/>
    </form>
    <a href="/product/info">product info get link</a>
</body>
</html>

5. 角色问题USER/ROLE_USER

当配置api需要USER权限时,在数据库中取到权限字符串“USER”,但是并不能成功获取到权限。

这属于框架的一个潜规则,需要用户的权限字符串前缀“ROLE_”,即在数据库中保存的权限应该为“ROLE_USER”。

 

-------end--------

 

 

 

 

posted on 2020-05-29 08:56  i野老i  阅读(584)  评论(0编辑  收藏  举报