SpringBoot 整合 SpringSecurity

该文章展示了如何在SpringBoot项目中集成SpringSecurity进行用户认证,创建实体类和拦截器来处理登录和权限。通过SessionInterceptor检查请求中的token,并使用UserDetails接口存储用户信息。同时,利用缓存管理用户session和token的关系。
摘要由CSDN通过智能技术生成

1. 项目目录

在这里插入图片描述

2. pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.6.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.6.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.6.3</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.46</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.30</version>
</dependency>
<!-- jackson-databind 和 starter-web 版本冲突 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
    <exclusions>
        <exclusion>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3. application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root

mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  type-aliases-package: com.cnbai.entity
  global-config:
    db-config:
      id-type: input
      db-column-underline: true
      refresh-mapper: true
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4. 创建配置类

CorsConfig

package com.cnbai.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 跨域请求配置
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许 cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的 header 属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}

DiscoverSecurityConfig

package com.cnbai.config;

import com.cnbai.handler.JwtAuthenticationTokenFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
 * SpringSecurity 配置类
 * - @EnableGlobalMethodSecurity : 开启基于注解的权限控制 -> @PreAuthorize
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DiscoverSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Resource
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private AccessDeniedHandler accessDeniedHandler;

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

    /** 认证管理 */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /** 核心过滤器 */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/resources/**")
                .antMatchers("/static/**");
    }

    /** 安全过滤器链 */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口,允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        // 添加过滤器
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        // 配置异常处理器
        http.exceptionHandling()
                // 配置认证失败处理器
                .authenticationEntryPoint(authenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler);

        // 允许跨域
        http.cors();
    }
}

MybatisPlusConfig

package com.cnbai.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

/**
 * Mybatis-plus 配置类
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    @Primary
    @Bean
    @ConfigurationProperties("mybatis-plus")
    public MybatisPlusProperties mybatisPlusProperties() {
        return new MybatisPlusProperties();
    }
}

5. 创建实体类

LoginUser

package com.cnbai.entity;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

/**
 * 实现 SpringSecurity 提供的 UserDetails 接口,保存登录信息
 */
@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

    private User user;

    /** 权限集合 */
    private List<String> permissions;

    public LoginUser(User user, List<String> permissions) {
        this.user = user;
        this.permissions = permissions;
    }

    /** 防止 redis 存储 loginUser 时序列化出错 */
    @JSONField(serialize = false)
    private List<SimpleGrantedAuthority> authorities;

    /** 用户的权限集,默认需要添加前缀 */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (authorities != null) {
            return authorities;
        }
        //把 permissions 中 String 类型的权限信息封装成 SimpleGrantedAuthority 对象
        authorities = new ArrayList<>();
        for (String permission : permissions) {
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permission);
            authorities.add(authority);
        }
        return authorities;
    }

    /** 用户的加密后的密码,不加密会使用前缀 */
    @Override
    public String getPassword() {
        return user.getPassword();
    }

    /** 应用内唯一的用户名 */
    @Override
    public String getUsername() {
        return user.getUsername();
    }

    /** 账户是否过期 */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /** 账户是否锁定 */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /** 凭证是否过期 */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /** 用户是否可用 */
    @Override
    public boolean isEnabled() {
        return true;
    }
}

Menu

package com.cnbai.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@TableName(value = "sys_menu")
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Menu implements Serializable {

    @TableId
    private Long id;
    private String menuName;
    private String path;
    private String perms;
}

User

package com.cnbai.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@TableName(value = "sys_user")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @TableId
    private Long id;
    private String username;
    private String password;
    private String roleId;
}

6. 创建业务类

LoginController

package com.cnbai.controller;

import com.cnbai.entity.User;
import com.cnbai.service.LoginService;
import com.cnbai.utils.ResponseResult;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * 登录
 */
@RestController
public class LoginController {

    @Resource
    private LoginService loginService;

    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user) {
        return loginService.login(user);
    }

    @PostMapping("/logout")
    public ResponseResult logout() {
        return loginService.logout();
    }

    @RequestMapping("/hello")
    @PreAuthorize("hasAuthority('admin')")
    public String hello() {
        return "hello";
    }

    @RequestMapping("/test")
    @PreAuthorize("hasAuthority('test')")
    public String test() {
        return "test";
    }

    @RequestMapping("/info")
    @PreAuthorize("hasAnyAuthority('test','admin','system:user:list')")
    public String info() {
        return "info";
    }

    /**
     * hasRole 要求有对应的角色才可以访问,但是它内部会把我们传入的参数拼接上 ROLE_ 后再去比较。
     * 所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以
     */
    @RequestMapping("/role")
    @PreAuthorize("hasRole('system:user:list')")
    public String role() {
        return "role";
    }

    /**
     * hasAnyRole 有任意的角色就可以访问,它内部会把我们传入的参数拼接上 ROLE_ 后再去比较。
     * 所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以
     */
    @RequestMapping("/anyRole")
    @PreAuthorize("hasAnyRole('admin','system:user:list')")
    public String anyRole() {
        return "anyRole";
    }
}

LoginService

package com.cnbai.service;

import com.cnbai.entity.User;
import com.cnbai.utils.ResponseResult;

public interface LoginService {

    ResponseResult login(User user);

    ResponseResult logout();
}

LoginServiceImpl

package com.cnbai.service;

import com.cnbai.entity.LoginUser;
import com.cnbai.entity.User;
import com.cnbai.utils.JwtUtils;
import com.cnbai.utils.ResponseResult;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    private AuthenticationManager authenticationManager;

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 通过 AuthenticationManager 的 authenticate 方法来进行用户认证,
     * 认证成功的话要生成一个 JWT,放入响应中返回,并且为了让用户下回请求时需要通过 JWT 识别出具体的是
     * 哪个用户,我们需要把用户信息存入 redis,把 userId 作为 key
     */
    @Override
    public ResponseResult login(User user) {
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        // 认证失败
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("认证失败");
        }

        // 认证成功
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        // 使用 userId 生成一个 jwt
        String jwt = JwtUtils.generateToken(userId);
        Map<String, String> map = new HashMap<>();
        map.put("token", jwt);

        // 把完整的用户信息存入 redis , userId 作为 key
        redisTemplate.opsForValue().set("login:" + userId, loginUser);
        return new ResponseResult(200, "登录成功", map);
    }


    /**
     * 这里并不需要删除 SecurityContextHolder 中的信息,只需要删除 redis 中所存储的即可,
     * 因为在进行认证的时候,需要先在 SecurityContextHolder 中拿到信息后,再从 redis 中获取对应信息。
     * 每个请求都对应一个 SecurityContextHolder,所以删除 SecurityContextHolder 中的信息是无效的
     */
    @Override
    public ResponseResult logout() {
        //获取 SecurityContextHolder 中的 用户id
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        Long userId = loginUser.getUser().getId();
        // 删除 redis 中的值
        redisTemplate.delete("login:" + userId);
        return new ResponseResult(200, "注销成功");
    }
}

UserDetailServiceImpl

package com.cnbai.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cnbai.dao.MenuDao;
import com.cnbai.dao.UserDao;
import com.cnbai.entity.LoginUser;
import com.cnbai.entity.User;
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 javax.annotation.Resource;
import java.util.List;
import java.util.Objects;

/**
 * 用户登录认证授权
 */
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Resource
    private UserDao userDao;

    @Resource
    private MenuDao menuDao;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, username);
        User user = userDao.selectOne(queryWrapper);
        // 如果没有查询到用户就抛出异常
        if (Objects.isNull(user)) {
            throw new RuntimeException("用户名或者密码错误");
        }

        // eq:List<String> list = new ArrayList<>(Arrays.asList("test","admin"));
        // 查询对应的权限信息
        List<String> list = menuDao.selectPermsByUserId(user.getId());
        // 把数据封装成 UserDetails 返回
        return new LoginUser(user, list);
    }
}

MenuDao

package com.cnbai.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cnbai.entity.Menu;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface MenuDao extends BaseMapper<Menu> {

    List<String> selectPermsByUserId(Long userId);
}

UserDao

package com.cnbai.dao;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cnbai.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserDao extends BaseMapper<User> {
}

7. 创建过滤器

AccessDeniedHandlerImpl

package com.cnbai.handler;

import com.alibaba.fastjson.JSON;
import com.cnbai.utils.ResponseResult;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定义授权失败处理
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
                       AccessDeniedException accessDeniedException) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "您的权限不足");
        String json = JSON.toJSONString(result);
        System.out.println("授权失败:" + json);
    }
}

AuthenticationEntryPointImpl

package com.cnbai.handler;

import com.alibaba.fastjson.JSON;
import com.cnbai.utils.ResponseResult;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 自定义认证失败处理
 */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
                         AuthenticationException authException) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "用户认证失败请查询登录");
        String json = JSON.toJSONString(result);
        System.out.println("认证失败:" + json);
    }
}

JwtAuthenticationTokenFilter

package com.cnbai.handler;

import com.cnbai.entity.LoginUser;
import com.cnbai.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
 * 自定义一个过滤器,去获取请求头中的 token,对 token 进行解析取出其中的 userId,
 * 使用 userId 去 redis 中获取对应的 LoginUser 对象,
 * 然后封装 Authentication 对象存入 SecurityContextHolder
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 这里先设置放行最后再使用 return 是因为,如果该请求不含 token,那么就对其进行放行,
     * 让请求进入下一个拦截器,后续 Security 框架还有很多拦截器可以对其进行验证,
     * 而使用 return 是因为后续在进行参数返回的时候,不需要再执行以下代码
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 获取 token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            // 放行
            filterChain.doFilter(request, response);
            return;
        }

        // 解析 token
        String userId;
        try {
            Claims claims = JwtUtils.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }

        //从 redis 中获取用户信息
        String redisKey = "login:" + userId;
        Object user = redisTemplate.opsForValue().get(redisKey);
        if (Objects.isNull(user)) {
            throw new RuntimeException("用户未登录");
        }
        LoginUser loginUser = (LoginUser) user;

        // 获取权限信息封装到 Authentication 中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        // 存入 SecurityContextHolder
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        // 放行
        filterChain.doFilter(request, response);
    }
}

8. 创建工具类

JwtUtils

package com.cnbai.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtUtils {

    // 进行数字签名的私钥
    private static final String secretKey = "123456";

    // 过期时间 : 24h
    private static final long time = 86400000;

    /**
     * 生成密钥
     */
    public static String generateToken(String userId) {
        return generateToken(userId, "admin");
    }

    /**
     * 生成密钥
     */
    public static String generateToken(String userId, String role) {
        Date now = new Date();
        return Jwts.builder()
                .setSubject(userId)
                .setIssuedAt(now)
                .setExpiration(new Date(now.getTime() + time))
                .claim("role", role)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    /**
     * 解析
     */
    public static Claims parseJWT(String jwt) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

ResponseResult

package com.cnbai.utils;

import lombok.Data;
import org.springframework.http.HttpStatus;

import java.io.Serializable;

@Data
public class ResponseResult<T> implements Serializable {

    private int code;
    private String msg;
    private T data;

    public ResponseResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResponseResult(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static ResponseResult success() {
        return ResponseResult.success(HttpStatus.OK.value(), "操作成功", null);
    }

    public static ResponseResult success(String msg) {
        return ResponseResult.success(HttpStatus.OK.value(), msg, null);
    }

    public static ResponseResult success(Integer code, String msg, Object data) {
        return new ResponseResult(code, msg, data);
    }

    public static ResponseResult error() {
        return ResponseResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), "操作失败", null);
    }

    public static ResponseResult error(String msg) {
        return ResponseResult.error(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, null);
    }

    public static ResponseResult error(Integer code, String msg, Object data) {
        return new ResponseResult(code, msg, data);
    }
}

9. 创建 Mapper

MenuMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.cnbai.dao.MenuDao">

    <select id="selectPermsByUserId" resultType="java.lang.String">
		SELECT 
			DISTINCT m.perms
		FROM
			sys_user_role ur
			LEFT JOIN sys_role r ON ur.role_id = r.id
			LEFT JOIN sys_role_menu rm ON ur.role_id = rm.role_id
			LEFT JOIN sys_menu m ON m.id = rm.menu_id 
		WHERE
			ur.user_id = #{userId}
    </select>
</mapper>

10. 创建启动类

Application

package com.cnbai;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.cnbai.dao")
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot整合Spring Security主要是为了提供安全控制功能,帮助开发者快速地在Spring Boot应用中添加身份验证、授权和会话管理等安全性措施。以下是基本步骤: 1. 添加依赖:首先,在Maven或Gradle项目中添加Spring Security的相关依赖到pom.xml或build.gradle文件中。 ```xml <!-- Maven --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Gradle --> implementation 'org.springframework.boot:spring-boot-starter-security' ``` 2. 配置WebSecurityConfigurerAdapter:在`src/main/resources/application.properties`或application.yml中配置一些基础属性,如启用HTTPS、密码加密策略等。然后创建一个实现了`WebSecurityConfigurerAdapter`的类,进行具体的配置,如设置登录页面、认证器、过滤器等。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/css/**", "/js/**", "/images/**").permitAll() // 允许静态资源访问 .anyRequest().authenticated() // 所有其他请求需要认证 .and() .formLogin() // 设置基于表单的身份验证 .loginPage("/login") // 登录页URL .defaultSuccessUrl("/") // 登录成功后的默认跳转URL .usernameParameter("username") .passwordParameter("password") .and() .logout() // 注销功能 .logoutUrl("/logout") .logoutSuccessUrl("/") .deleteCookies("JSESSIONID"); } // ... 其他配置如自定义用户DetailsService、密码编码器等 } ``` 3. 用户服务(UserDetailsService):如果需要从数据库或其他数据源获取用户信息,需要实现`UserDetailsService`接口并提供用户查询逻辑。 4. 运行应用:启动Spring Boot应用后,Spring Security将自动处理HTTP请求的安全检查,例如身份验证和授权。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值