SpringBoot整合SpringSecurity

什么是SpringSecurity?

Spring Security是Spring提供的一套web的应用安全性的完整解决方案。

SpringSecurity采用责任式链的设计模式,它的核心是一组过滤器链。

主要包括:

  • 认证(Authentication):什么是认证?简单的说就是检验某个用户是否为系统合法用户,需要用户提供用户名和密码进行校验当前用户能否访问系统。
  • 授权(Authorization):就是当前用户?是否具有某种权限来进行某种操作。

SpringSecurity的认证流程

  1. 用户在浏览器中访问一个需要认证的URL。
  2. Spring Security会检查用户是否已经被认证(即是否已登录)。如果用户已经认证,那么他们可以正常访问该URL。如果用户未被认证,那么他们将被重定向到loginPage()对应的URL,通常是登录页面。
  3. 用户在登录页面输入用户名和密码,然后点击登录按钮,发起登录请求。
  4. 如果请求的URL和loginProcessingUrl()一致,那么Spring Security将开始执行登录流程。否则,用户可能需要重新进行认证。
  5. 在执行登录流程时,首先会经过UsernamePasswordAuthenticationFilter过滤器,该过滤器会取出用户输入的用户名和密码,然后将其放入一个容器(UsernamePasswordAuthenticationToken)中。这个容器会被用于后续的认证流程。
  6. 接下来,UsernamePasswordAuthenticationToken会被提交给AuthenticationManager进行管理。AuthenticationManager会委托给AuthenticationProvider进行具体的认证操作。在这个过程中,可能会涉及到密码的加密和比对。
  7. 如果认证成功,那么用户的认证信息将被存储在session中,并且用户可以正常访问他们之前试图访问的URL。如果认证失败,那么用户可能会被重定向到失败URL(如果配置了的话),或者可能会看到一个错误页面

代码示例

简单案例

1,引入maven依赖

复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2,启动服务

3,访问 http://localhost:8080/ 会自动跳转进入登录页面 默认用户是 user 密码为服务后端启动显示的安全密码

4,登录成功访问我们提供的接口

5,退出登录 访问 http://localhost:8080/logout

完整案例

1,引入maven依赖

XML 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2,User类

java 复制代码
package com.security.domain;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

/**
 * @Program: SpringBoot
 * @ClassName User
 * @Author: liutao
 * @Description: 用户类
 * @Create: 2023-06-11 17:22
 * @Version 1.0
 **/

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("sys_user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(type = IdType.AUTO)
    @ApiModelProperty("用户id")
    private Integer id;
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;
    @ApiModelProperty("性别")
    private int sex;
    @ApiModelProperty("年龄")
    private int age;


}

3,实现UserDetails接口并重写所有方法

java 复制代码
package com.security.domain;

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.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Program: SpringBoot
 * @ClassName LoginUser
 * @Author: liutao
 * @Description: 登录用户
 * @Create: 2023-06-11 18:17
 * @Version 1.0
 **/

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {
    private User user;
    private List<String> permissions;
    @JSONField(serialize = false)
    private List<GrantedAuthority> authorities;

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

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (authorities != null) {
            return authorities;
        }
//        authorities = new ArrayList<GrantedAuthority>();
//        // 封装权限信息 -》GrantedAuthority
//        permissions.forEach(s -> {
//            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(s);
//            authorities.add(simpleGrantedAuthority);
//        });

        authorities = permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        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;
    }
}

4,创建业务接口实现 UserDetailService接口 重写loadUserByUser(String s) 完成业务认证和授权

java 复制代码
package com.security.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.security.domain.LoginUser;
import com.security.domain.Role;
import com.security.domain.User;
import com.security.domain.UserRole;
import com.security.mapper.RoleMapper;
import com.security.mapper.UserMapper;
import com.security.mapper.UserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Program: SpringBoot
 * @ClassName service
 * @Author: liutao
 * @Description: 用户业务接口
 * @Create: 2023-06-11 17:43
 * @Version 1.0
 **/

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper mapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<User>();
        lambdaQueryWrapper.eq(User::getUsername, s);
        User user = mapper.selectOne(lambdaQueryWrapper);
        if (Objects.isNull(user)) {
            throw new UsernameNotFoundException("用户不存在");
        }
        //TODO  查询对应角色和权限信息 注意角色前一定要以"ROLE_"开头
//        List<String> auths = new ArrayList<String>(Arrays.asList("ROLE_USER","sys:user"));
        List<String> auths = new ArrayList<String>(Collections.emptyList());
        // 通过用户id获取当前用户所有角色id
        List<Integer> ids = userRoleMapper.selectList(
                new LambdaQueryWrapper<UserRole>()
                        .eq(UserRole::getUserId, user.getId())
                )
                .stream()
                .map(UserRole::getRoleId)
                .collect(Collectors.toList());
        // 获取角色
        List<String> roles = roleMapper.selectBatchIds(ids)
                .stream()
                .map(Role::getFlag)
                .collect(Collectors.toList());
        auths.addAll(roles);
        auths.add("sys:user");
        return new LoginUser(user, auths);
    }


}

6,配置核心配置文件

java 复制代码
package com.security.config;

import com.security.filter.JwtAuthenticationTokenFilter;
import com.security.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @Program: SpringBoot
 * @ClassName SecurityConfig
 * @Author: liutao
 * @Description: security配置类
 * @Create: 2023-06-11 17:39
 * @Version 1.0
 **/

@Configuration
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 自定义用户认证
//        auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");

        // 数据库用户认证
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * anyRequest          |   匹配所有请求路径
     * access              |   SpringEl表达式结果为true时可以访问
     * anonymous           |   匿名可以访问
     * denyAll             |   用户不能访问
     * fullyAuthenticated  |   用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority     |   如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole          |   如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority        |   如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress        |   如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole             |   如果有参数,参数表示角色,则其角色可以访问
     * permitAll           |   用户可以任意访问
     * rememberMe          |   允许通过remember-me登录的用户访问
     * authenticated       |   用户登录后可访问
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // CSRF禁用,因为不使用session
                .csrf().disable()
                // 禁用HTTP响应标头
                .headers().cacheControl().disable().and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录toLogin 允许匿名访问
                .mvcMatchers("/toLogin").permitAll().mvcMatchers("/css/**", "/js/**", "/swagger-resources/**", "/webjars/**", "/v2/**", "/doc.html", "/img/**", "/favicon.ico").permitAll()
//                .antMatchers("/**")
//                .hasAnyRole("USER")
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
//                .and()
//                .formLogin()
//                .loginProcessingUrl("/toLogin")
//                .successForwardUrl("/home")
//                .and()
//                .httpBasic();
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }


    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

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

7,抽离web接口代码

java 复制代码
package com.security.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.security.common.Result;
import com.security.domain.LoginUser;
import com.security.domain.User;
import com.security.domain.dto.UserDto;
import com.security.domain.vo.UserVo;
import com.security.mapper.UserMapper;
import com.security.service.UserService;
import com.security.utils.JwtUtil;
import com.security.utils.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Program: SpringBoot
 * @ClassName UserServiceImpl
 * @Author: liutao
 * @Description:
 * @Create: 2023-06-12 00:51
 * @Version 1.0
 **/

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
    @Autowired
    private UserMapper mapper;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public UserVo login(UserDto userDto) {
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDto.getUsername(), userDto.getPassword());
        Authentication authentication = authenticationManager.authenticate(token);
        if (Objects.isNull(authentication)) {
            throw new RuntimeException("登录失败!");
        }
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        if (!passwordEncoder.matches(userDto.getPassword(), loginUser.getPassword())) {
            throw new RuntimeException("密码错误");
        }
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(loginUser.getUser(), userVo);
        String jwtToken = JwtUtil.getToken(userVo.getId().toString());
        String redisKey = "login:" + userVo.getId().toString();
        log.info("当前登录用户:{}",loginUser);
        redisUtil.set(redisKey, loginUser,20L, TimeUnit.MINUTES);
        userVo.setToken(jwtToken);
        return  userVo;
    }
}

8,web接口

java 复制代码
package com.security.controller;

import com.security.common.Result;
import com.security.domain.dto.UserDto;
import com.security.domain.vo.UserVo;
import com.security.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

/**
 * @author TAOGE
 */
@RestController
public class BasicController {

    @Autowired
    private UserService userService;
    @PostMapping("/toLogin")
    public Result<UserVo> login(@RequestBody UserDto userDto) {
        return  Result.success("登录成功", userService.login(userDto));
    }

    @PreAuthorize("hasRole('ADMIN')")
    @GetMapping("/hasRole")
    public Result<String> hasRole() {
        return Result.success("ADMIN");
    }

    @PreAuthorize("hasAuthority('sys:user')")
    @GetMapping("/hasPerm")
    public Result<String> hasPerm() {
        return Result.success("sys:user");
    }
}

测试

1,启动服务

2,访问 /hasRole接口

3,认证授权

4,携带token访问 /hasRole 和 /hasPerm接口

结尾

到这里我们今天的SpringBoot整合和尝鲜SpringSecurity就结束了!!

欢迎双击点赞 666!!!

everybody see you!!!

相关推荐
十铭忘2 分钟前
基于SAM2的眼动数据跟踪2
java·服务器·前端
okjohn12 分钟前
浅谈需求分析与管理
java·架构·系统架构·软件工程·产品经理·需求分析·规格说明书
寻kiki13 分钟前
python test transpose hstack vstack...
后端
shengjk118 分钟前
搞不懂去中心化、主从架构和 HA?1 分钟理清关系,再也不怕被问架构设计
后端
用户03321266636726 分钟前
Java添加、设置和删除PDF图层:
java
PFinal社区_南丞27 分钟前
开源开发者必备-toilet终端ASCII艺术字工具
后端
我不是混子28 分钟前
Springboot整合Druid
后端
荣光波比34 分钟前
K8S(十)—— Kubernetes核心组件详解:Pod控制器与配置资源管理
java·容器·kubernetes
m0_6515939142 分钟前
企业级订单系统架构设计:领域驱动 vs 数据驱动实践指南
java·系统架构·领域驱动ddd
WangMing_X43 分钟前
C#上位机软件:2.5 体验CLR实现多语言混合编程
java·开发语言·c#