SpringSecurity安全框架


前言

Spring Security是一款主要解决了认证和授权相关处理的安全框架

认证: 验证用户身份, 例如在登录过程中验证用户名与密码是否匹配
授权: 使得用户允许访问服务器的某些资源, 或禁止访问某些资源

Spring Boot Security是在Spring Boot中使用的, 基于Spring Security的依赖项, 其本质就是Spring Security框架加上了应用于Spring Boot工程的自动配置


提示:以下是本篇文章正文内容,下面案例可供参考

一、创建项目

新建Spring Boot项目, 勾选spring web和spring security
在这里插入图片描述

项目中Spring Security 在pom.xml中的依赖为

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

添加依赖后, 启动项目, 在访问所有资源时, 都是要求先登录(未退出之前只需要登录1次)

二、认证登录

1.默认账号密码

默认的用户名是user, 密码会在启动时输出在控制台, 例如: 启动项目
在这里插入图片描述

Useing generated security password: 密码, ":"后面就是密码, 账号为user, 浏览器访问http://localhost:8080/, 输入账号密码

在这里插入图片描述

2.自定义账号密码

在项目默认配置文件application.properties中配置以下信息:

#配置security登录账号
spring.security.user.name=root
#配置security登录密码
spring.security.user.password=root

此时security登录账号密码为 : root/root, 控制台不在输出随机密码

bcrypt加密

使用security验证密码时, 密码必须被框架中的BCryptPasswordEncoder类加密过
Security框架推荐使用Bcrypt算法对密码原文进行加密处理, 框架中有BCryptPasswordEncoder类, 此类可以实现加密, 判断密码是否匹配等功能, 使用的是Bcrypt算法, 每次加密后密文都不相同, 因为里面加入了随机盐值, 盐值保存在密文中, 也可以正常验证

验证方法

Spring Security会在处理登录认证时自动根据尝试登录的用户名调UserDetailsService接口实现类的loadUserByUsername此方法, 并获得UserDetails对象, 此对象包含用户的密码、权限等信息, 接下来, Spring Security会自动判断密码, 如果不正确, 将返回错误信息, 如果正确, 会将此用户信息(包含权限)保存下来(默认保存在Session中)

比对密码时, 对比的密文必须经过Bcrypt算法加密

三、关于授权

需要在security配置类上, 添加@EnableGlobalMethodSecurity(perPostEnabled=true)注解, 启用控制器处理方法权限验证

并让security配置类继承WebSecurityConfigurerAdapter(web安全配置适配器), 并重写configure(HttpSecurity http)方法, 里面配置关闭跨域(前后端分离), 启用登录时表单验证和配置对需要的请求验证, 不需要验证的请求直接通过

最后在控制器方法上添加@PerAuthorize("hasAuthority('权限')")注解, 否则访问会出现403没有权限访问的错误

四、关于Session

HTTP协议是一种无状态的协议, 同一个用户在同一台设备上多次对同一个服务器端进行访问时, 默认在服务器端并不保存此用户的相关信息, 所以, 无论访问多少次, 服务器端都无法识别用户的身份

为了解决服务器端可以识别用户身份, 可以使用Session(会话), 当某个客户端第1次向服务器端发送请求后, 服务器端会在其内存中保存此用户的信息, 并且此信息会关联到一个唯一的SessionI D, 当服务器端进行响应时, 会将此Session ID响应到客户端, 后续, 客户端应该在每次请求时都携带此Session ID, 则服务器可以根据后续请求头中的Session ID对应到此前保存的数据, 从而识别用户身份

使用Session保存的数据, 通常是:

  • 用户身份的唯一标识, 例如用户的ID
  • 高频率使用的数据, 例如用户的权限(有数据不一致的风险)
  • 不便于使用其他存储技术进行处理的数据

Session消失的机制

  • 超时, 如果客户端长时间未向服务器端发起任何请求, 则在服务器端上, 此客户端对应的Session数据会被清除, 常见的设置值是15分钟或30分钟
  • 更换客户端(包括更换浏览器、关开浏览器), 也会无法访问此前的Session数据, 并且, 此前的Session数据将会根据超时机制被清理
  • 服务器端设备关机或重启
  • 服务器端程序调用了清除Session数据的方法, 例如调用了HttpSession对象的invalidate()方法

五、项目演示

新建VO类, 模拟从数据库查询到的数据信息

package cn.qingtian.security.pojo.vo;

import java.util.List;

public class UserLoginVO {
    // 用户名
    private String username;
    // 密码
    private String password;
    // 模拟用户权限
    private List<String> permissions;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<String> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<String> permissions) {
        this.permissions = permissions;
    }
}

配置Security配置类

package cn.qingtian.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

// Security配置类, 用户生成BCryptPasswordEncoder实例
@Configuration
// security中启用全局方法安全, 用于限制User对控制器方法的访问权限
@EnableGlobalMethodSecurity(prePostEnabled = true)
// 此类继承web安全配置适配器
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    // 实现WebSecurityConfigurerAdapter中的configure方法
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //配置不需要登录就能访问的请求
        String[] url = {};

        // 关闭跨域
        http.csrf().disable();

        // 配置授权请求
        http.authorizeRequests()
                // 配置不需要登录就能访问的请求
                .antMatchers(url).permitAll()
                // 除不需要登录就能访问的请求外, 所有请求都需要登录和授权
                .anyRequest().authenticated();

        // 当登录时启用表单登录
        http.formLogin();
    }
}

创建uril包, 实现全局密码加密

package cn.qingtian.security.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class GlobalPasswordEncoder {

    @Autowired
    private PasswordEncoder passwordEncoder;

    public String encode(String rowPassword){
        return passwordEncoder.encode(rowPassword);
    }
}

实现认证类UserDetailsService, 并重写loadUserByUsername方法

package cn.qingtian.security.service.impl;

import cn.qingtian.security.pojo.vo.UserLoginVO;
import cn.qingtian.security.util.GlobalPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.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 java.util.ArrayList;
import java.util.List;

// security认证类
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    // 加载全局密码编码器
    @Autowired
    private GlobalPasswordEncoder globalPasswordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 模拟从数据持久层查询到的数据
        UserLoginVO user = new UserLoginVO();
        user.setUsername("admin");
        // 此密码需要用Security中的BCrypt加密
        String password = globalPasswordEncoder.encode("123456");
        System.out.println(password);
        user.setPassword(password);
        // 模拟权限数据
        List<String> permissions = new ArrayList<>();
        permissions.add("/list");
        user.setPermissions(permissions);

        // 授予权限集合
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

        for(String permission : permissions){
            // GrantedAuthority实现类 SimpleGrantedAuthority
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
            grantedAuthorities.add(simpleGrantedAuthority);
        }

        //判断方法传入登录用户名是否在数据库中
        if (!user.getUsername().equals(username)){
            // security验证中的异常 BadCredentialsException
            throw new BadCredentialsException("用户名不存在");
        }

        // User为security中的类, 此类会被security保存在Session中(默认)
        return User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                // 授予权限
                .authorities(grantedAuthorities)
                // 账号是否过期
                .accountExpired(false)
                // 禁用
                .disabled(false)
                // 凭证是否过期
                .credentialsExpired(false)
                .build();
    }
}

编写处理器方法

package cn.qingtian.security.controller;

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

//控制器层
@RestController
public class UserController {

    //@PreAuthorize("hasAnyAuthority(String...)")预授权注解(有任何权限)
    @PreAuthorize("hasAnyAuthority('/list')")
    @RequestMapping("/list")
    public String list(){
        return "用户列表获取成功";
    }
}

最后启动效果

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值