Fork me on GitHub

SpringSecurity

SpringSecurity

image
image

认证(Authentication)

pom.xml

在pom.xml中引入springboot及springsecurity

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>
    <dependencies>
        <!-- springsecurity -->
        <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>
    </dependencies>

application.yaml配置文件

spring:
  # Spring Security 配置项,对应 SecurityProperties 配置类
  security:
    # 配置默认的 InMemoryUserDetailsManager 的用户账号与密码。
    user:
      name: admin # 账号
      password: 123456 # 密码
      roles: ADMIN # 拥有角色

代码

package com.autumn.springsecurity.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.security.PermitAll;

@RestController
@RequestMapping("/hellocontroller")
public class HelloController {

    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }

    @GetMapping("/home")
    public String home() {
        return "首页";
    }

    @GetMapping("/admin")
    public String admin() {
        return "管理员";
    }

    @GetMapping("/normal")
    public String normal() {
        return "普通用户";
    }
}

启动springboot项目,http://localhost:8080登录界面输入admin 123456认证后才可访问http://localhost:8080/hellocontroller/hello等一系列接口
image

鉴权配置SecurityConfig

package com.autumn.springsecurity.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;

@Configuration
//允许使用注解方式配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 授权逻辑,控制了“什么角色能访问什么资源”
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //授权配置
        http
            // 配置请求地址的权限
            .authorizeRequests()
            .antMatchers("/hellocontroller/home").permitAll() // 所有用户可访问
            .antMatchers("/hellocontroller/admin").hasRole("ADMIN") // 需要 ADMIN 角色
            .antMatchers("/hellocontroller/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。
            // 任何请求,访问的用户都需要经过认证
            .anyRequest().authenticated()
            .and()
            // 设置 Form 表单登陆
            .formLogin()
//                    .loginPage("/login") // 登陆 URL 地址
            .permitAll() // 所有用户可访问
            .and()
            // 配置退出相关
            .logout()
//                    .logoutUrl("/logout") // 退出 URL 地址
            .permitAll(); // 所有用户可访问
    }

    /**
     * 认证逻辑
     * 配置用户名密码及角色
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.
            // 使用内存中的 InMemoryUserDetailsManager
            inMemoryAuthentication()
            // 不使用 PasswordEncoder 密码编码器
            .passwordEncoder(NoOpPasswordEncoder.getInstance())
            // 配置 admin 用户,会覆盖application.yaml 中的配置的admin账号
            .withUser("admin").password("111").roles("ADMIN")
            // 配置 normal 用户
            .and().withUser("user").password("111").roles("NORMAL");
    }

}

登录user账号
image
访问admin接口,会显示403(因为admin接口需要admin角色,而user是normal角色)
image

注解方式

添加@EnableGlobalMethodSecurity(prePostEnabled = true)

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter

Controller使用权限注解

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.security.PermitAll;

@RestController
@RequestMapping("/ss")
public class SSController {

    @PermitAll
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    @GetMapping("/home")
    public String home() {
        return "首页";
    }

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("/admin")
    public String admin() {
        return "管理员";
    }

    @PreAuthorize("hasRole('ROLE_NORMAL')")
    @GetMapping("/normal")
    public String normal() {
        return "普通用户";
    }

}

登录admin账户
image
可以访问认证接口和拥有角色接口
image

权限控制

方法名 说明 适用场景 常用程度
permitAll() 所有用户都可以访问 公共资源,如登录页、首页、静态资源 ⭐⭐⭐⭐
denyAll() 所有用户都不可以访问 屏蔽某些接口
authenticated() 只允许已登录用户访问 需要身份认证的接口 ⭐⭐⭐⭐
anonymous() 允许匿名用户访问(未登录即可访问) 注册页、找回密码页 ⭐⭐
rememberMe() 通过 “记住我” 登录的用户可访问 提供记住登录功能的场景 ⭐⭐
fullyAuthenticated() 必须是完全登录(非 remember-me)用户 敏感操作,如修改密码 ⭐⭐
hasIpAddress(String ip) 限制特定 IP 的用户访问 接口白名单、公司内网 ⭐⭐
hasRole(String role) 拥有指定角色的用户可访问 RBAC 基于角色的访问控制 ⭐⭐⭐⭐⭐
hasAnyRole(String... roles) 拥有任意一个角色即可访问 多角色支持,如 ADMINUSER ⭐⭐⭐⭐⭐
hasAuthority(String authority) 拥有指定权限的用户可访问 RBAC 基于权限的访问控制 ⭐⭐⭐⭐⭐
hasAnyAuthority(String... authorities) 拥有任意一个权限即可访问 多权限支持,如 READWRITE ⭐⭐⭐⭐⭐
access(String expression) 使用 SpEL 表达式实现更复杂的权限控制 复杂业务逻辑,如根据用户属性动态判断 ⭐⭐⭐
not().hasRole(String role) 没有某个角色的用户可访问 限制管理员访问普通接口
isRememberMe() 判断用户是否通过 RememberMe 登录 个性化权限
isAnonymous() 判断用户是否匿名 区分登录与未登录状态
isAuthenticated() 判断用户是否已登录 常用于表达式中 ⭐⭐⭐
principal 获取当前登录用户对象 在 SpEL 中动态获取用户信息 ⭐⭐⭐
hasPermission(Object target, Object permission) 基于 ACL 的权限控制 精细化到数据级别的安全控制 ⭐⭐

使用数据库动态配置认证和授权(未经测试)

SpringBoot2.5版本,使用继承WebSecurityConfigurerAdapter方法

数据库

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(64) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    enabled TINYINT DEFAULT 1
);

-- 角色表
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    code VARCHAR(64) NOT NULL UNIQUE COMMENT 'ROLE_ADMIN / ROLE_USER',
    name VARCHAR(64) NOT NULL
);

-- 用户-角色关系表
CREATE TABLE user_roles (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY(user_id, role_id)
);

-- 权限表(接口)
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    url VARCHAR(255) NOT NULL,
    method VARCHAR(10) DEFAULT 'GET',
    description VARCHAR(128)
);

-- 角色-权限关系表
CREATE TABLE role_permissions (
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    PRIMARY KEY(role_id, permission_id)
);

SecurityConfig(Spring Boot 2.5.0)

package com.autumn.springsecurity.config;

import com.autumn.springsecurity.service.CustomUserDetailsService;
import com.autumn.springsecurity.service.DynamicSecurityService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //用户和角色绑定
    private final CustomUserDetailsService userDetailsService;
    //角色和菜单绑定
    private final DynamicSecurityService dynamicSecurityService;

    /**
     * 密码加密器
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置认证方式
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(passwordEncoder());
        auth.authenticationProvider(provider);
    }

    /**
     * 配置 URL 权限规则
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable() // 开发阶段关闭 CSRF
            .authorizeRequests()
                // 1. 登录、注册、错误页面直接放行
                .antMatchers("/login", "/register", "/error").permitAll()

                // 2. 动态加载数据库中的 URL -> 角色映射
                .antMatchers(dynamicSecurityService.loadUrlRoleMappings().keySet()
                        .toArray(new String[0]))
                .access("@dynamicSecurityService.hasPermission(request, authentication)")

                // 3. 其他请求需要登录
                .anyRequest().authenticated()
            .and()
                .formLogin()
                .loginPage("/login")              // 自定义登录页
                .loginProcessingUrl("/doLogin")   // 登录接口
                .defaultSuccessUrl("/index", true) // 登录成功跳转
                .failureUrl("/login?error")
                .permitAll()
            .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .permitAll();
    }
}

CustomUserDetailsService认证

package com.autumn.springsecurity.service;

import com.autumn.springsecurity.entity.User;
import com.autumn.springsecurity.mapper.UserMapper;
import lombok.RequiredArgsConstructor;
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.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. 查询用户
        User user = userMapper.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }

        // 2. 查询角色
        List<String> roles = userMapper.findRolesByUserId(user.getId());

        // 3. 转换为 Spring Security 权限对象
        List<GrantedAuthority> authorities = roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());

        return org.springframework.security.core.userdetails.User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .authorities(authorities)
                .disabled(!user.getEnabled())
                .build();
    }
}

DynamicSecurityService(动态权限加载)

package com.autumn.springsecurity.service;

import com.autumn.springsecurity.mapper.PermissionMapper;
import com.autumn.springsecurity.dto.PermissionRoleDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class DynamicSecurityService {

    private final PermissionMapper permissionMapper;

    /**
     * 加载 URL -> 角色 映射
     */
    public Map<String, String[]> loadUrlRoleMappings() {
        List<PermissionRoleDTO> list = permissionMapper.findAllUrlRoleMappings();

        return list.stream().collect(Collectors.groupingBy(
                PermissionRoleDTO::getUrl,
                Collectors.mapping(PermissionRoleDTO::getRoleCode, Collectors.toList())
        )).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toArray(new String[0])));
    }

    /**
     * 判断当前用户是否有访问权限
     */
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        Map<String, String[]> urlRoleMappings = loadUrlRoleMappings();
        String requestURI = request.getRequestURI();
        if (!urlRoleMappings.containsKey(requestURI)) {
            return true; // 未配置的接口默认放行
        }

        String[] roles = urlRoleMappings.get(requestURI);
        return authentication.getAuthorities().stream()
                .anyMatch(a -> Arrays.asList(roles).contains(a.getAuthority()));
    }
}

Mapper 层

@Mapper
public interface PermissionMapper {

    @Select("""
        SELECT p.url, r.code AS roleCode
        FROM permissions p
        JOIN role_permissions rp ON p.id = rp.permission_id
        JOIN roles r ON r.id = rp.role_id
    """)
    List<PermissionRoleDTO> findAllUrlRoleMappings();
}
posted @ 2025-09-01 21:26  秋夜雨巷  阅读(7)  评论(0)    收藏  举报