项目集成 Spring Security 做登录鉴权(一)

1,100 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

为什么需要这个框架

  • 后端的接口不方便对外开放,需要后端做权限校验。例如后台管理系统接口访问需要 token,或不同角色需要访问不同的内容等。
  • 拥抱 Spring 开源,学习 Spring Security 为后续微服务中的 Oauth2 打下基础。

创建项目

打开 IDEA 工具,操作菜单 文件 -> 新建 -> 项目

image.png 点击 下一步, 点击 创建

最终项目目录

image.png

引入 Maven 依赖

<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.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

tips: Maven 下载与加载依赖很慢,建议喝口茶等等,不推荐阿里源,之前踩过坑,静心慢慢等一会。

安全配置

目前很多文章,是老版本,新版本的 Spring Security 弃用了继承 WebSecurityConfigurerAdapter.
更推荐使用 lamda 风格编写的代码风格,与组件化配置。
相关文献见链接:spring.io/blog/2022/0…

根据 Spring Security 5.7.0-M2 后版本建议,新建 SecurityConfiguration.java 配置类,如下:

粘贴文件后,你会发现存在错误,不要慌,后面有此代码引用的类放上

package com.example.auth.config;

import com.example.auth.service.AdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * desc: 保护接口组件
 * date 2022/7/25
 *
 * @author 程序员鱼丸
 **/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    private static final String[] IGNORE_API = new String[]{"/user", "/login"};

    // 引入用户服务业务层, 使用 setter 注入
    private AdminService userDetailsService;

    @Autowired
    public void setUserDetailsService(AdminService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests(authz ->
                        // 放行一些接口,在 IGNORE_API 中
                        authz.antMatchers(IGNORE_API).permitAll()
                                // 除了放行的接口其他全校验
                                .anyRequest().authenticated()
                )
                // 使用 Spring Security 提供的默认值启用安全功能
                .httpBasic(withDefaults());
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 密码的加密方式
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        // 指定密码加密方式
        authProvider.setPasswordEncoder(passwordEncoder());
        // 指定用户业务层,此业务层需要实现 Spring Security 官方的 UserDetailsService 接口
        authProvider.setUserDetailsService(userDetailsService);
        return authProvider;
    }

}

实体类与业务实现类

实体类: AdminUser.java

package com.example.auth.domain;

import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

/**
 * desc: 需要实现官方接口 UserDetails
 * date 2022/7/25 15:57
 *
 * @author 程序员鱼丸
 **/
@Data
public class AdminUser implements UserDetails {

    private String username;

    private String password;


    /**
     * 账户的权限,例如 ["ROLE_ADMIN","ROLE_USER"]
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return new ArrayList<>();
    }

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

    @Override
    public String getUsername() {
        return this.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;
    }
}

业务实现类:AdminService.java

package com.example.auth.service;

import com.example.auth.domain.AdminUser;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AdminService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) {
        // 这里伪代码查询数据库
        if ("admin".equals(username)) {
            AdminUser adminUser = new AdminUser();
            adminUser.setUsername("admin");
            adminUser.setPassword(new BCryptPasswordEncoder().encode("123456"));
            return adminUser;
        } else {
            throw new UsernameNotFoundException("用户名密码错误");
        }
    }
}

提供 API 用来测试权限

控制层访问类:ApiController.java

package com.example.auth.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ApiController {

    @GetMapping("admin")
    public String getAdmin() {
        return "需要登录验证权限";
    }

    @GetMapping("user")
    public String getUser() {
        return "不需要登录验证权限";
    }

}

启动 Spring Boot 项目并访问接口测试

tips: 不会启动的去网页搜一下,idea 配置 springboot 启动.

首先,我们先访问: http://127.0.0.1:13921/user, 很显然直接能访问到

image.png

为啥呢?就知道你复制的时候没看代码,带你复习下

image.png
我们看到上面,user 接口被我们放行了,下面我们来请求 admin 接口

image.png 提示需要登陆了,我们输入在业务实现类中配置的用户名密码,不记得的回去看看

image.png 可以看到能访问到了,此时用的是 session 鉴权,这节课就先到这里

总结

代码 Git 仓库:github.com/cuifuan/aut…

    1. 编写 Security 安全配置类
    1. 实现 UserDetails 接口自定义 loadUserByUsername 接口
    1. 自定义修改安全配置类

展望未来

现如今,前后端分离是主要趋势,我们的登录接口后面需要定义为 Restful 风格登录,并对一些错误进行自定义。

  • 登录接口 Restful 化
  • 错误自定义
  • 集成 JWT