SpringBoot整合SpringSecurity

本文最后更新于2024.04.23-04:23,某些文章具有时效性,若有错误或已失效,请在下方留言或联系涛哥

什么是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依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

2,User类

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接口并重写所有方法

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) 完成业务认证和授权

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,配置核心配置文件

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接口代码

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接口

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

阅读剩余
THE END