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!!!

相关推荐
不会编程的懒洋洋13 分钟前
Spring Cloud Eureka 服务注册与发现
java·笔记·后端·学习·spring·spring cloud·eureka
赖龙18 分钟前
java程序打包及执行 jar命令及运行jar文件
java·pycharm·jar
U12Euphoria20 分钟前
java的runnable jar采用exe和.bat两种方式解决jre环境的问题
java·pycharm·jar
java小吕布40 分钟前
Java Lambda表达式详解:函数式编程的简洁之道
java·开发语言
NiNg_1_23441 分钟前
SpringSecurity入门
后端·spring·springboot·springsecurity
程序员劝退师_1 小时前
优惠券秒杀的背后原理
java·数据库
java小吕布1 小时前
Java集合框架之Collection集合遍历
java
一二小选手1 小时前
【Java Web】分页查询
java·开发语言
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 小时前
idea 弹窗 delete remote branch origin/develop-deploy
java·elasticsearch·intellij-idea
Code成立1 小时前
《Java核心技术 卷I》用户图形界面鼠标事件
java·开发语言·计算机外设