SpringSecurity1-基本使用

 SpringSecurity通常用于B端用户的认证和授权。比如在后端管理界面,不同的管理员有不同的权限。 认证解决的是登录-你是谁的问题; 授权解决的是权限-你能做什么的问题。

SringSecurity使用

搭建springboot工程,引入security的包,编写一个controller,启动项目。 访问controller时会有个会被security拦截,跳转默认登录页面。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.7.0</version>
        </dependency>
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/demo")
public class SecurityController {

    @GetMapping("/hello")
    public String hello() {
        return "hello security!";
    }
}

下面使用security来做一个完整的项目,先做好准备工作:

1、:   准备一个自己项目的前端界面和资源

2:添加pom依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>sringboot_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sringboot_demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <!--自带的log4j依赖有漏洞,需要使用log4j2-->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--log4j2-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--添加thymeleaf依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--添加lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--添加mp 依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!--添加mysql 依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.21</version>
        </dependency>
        <!--添加redis 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--基于redis实现session的依赖 -->
<!--        <dependency>-->
<!--            <groupId>org.springframework.session</groupId>-->
<!--            <artifactId>spring-session-data-redis</artifactId>-->
<!--        </dependency>-->

        <!--添加thymeleaf为SpringSecurity提供的标签 依赖 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.7.0</version>
            </plugin>
        </plugins>
    </build>

</project>

3:建立数据库表:

/*权限表*/
DROP TABLE IF EXISTS `t_permission`; CREATE TABLE `t_permission`(`ID` int(11)  NOT NULL AUTO_INCREMENT COMMENT '编号', `permission_name` varchar(30)  CHARACTER
   SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称', `permission_tag` varchar(30)  CHARACTER
   SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标签', `permission_url` varchar(100)  CHARACTER
   SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限地址', PRIMARY KEY(`ID`)  USING BTREE)  ENGINE= InnoDB AUTO_INCREMENT= 9 CHARACTER
   SET= utf8 COLLATE= utf8_general_ci ROW_FORMAT= Compact;


   /*角色表*/
 DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role`(`ID` int(11)  NOT NULL AUTO_INCREMENT COMMENT '编号', `ROLE_NAME` varchar(30)  CHARACTER
   SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称', `ROLE_DESC` varchar(60)  CHARACTER
   SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述', PRIMARY KEY(`ID`)  USING BTREE)  ENGINE= InnoDB AUTO_INCREMENT= 6 CHARACTER
   SET= utf8 COLLATE= utf8_general_ci ROW_FORMAT= Compact; 


/*用户表*/
 DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user`(`id` int(11)  NOT NULL AUTO_INCREMENT, `username` varchar(50)  CHARACTER
   SET utf8 COLLATE utf8_bin NULL DEFAULT NULL, `password` varchar(100)  CHARACTER
   SET utf8 COLLATE utf8_bin NULL DEFAULT NULL, `status` int(1)  NULL DEFAULT NULL COMMENT '用户状态1-启用 0-关闭', PRIMARY KEY(`id`)  USING BTREE)  ENGINE= InnoDB AUTO_INCREMENT= 6 CHARACTER
   SET= utf8 COLLATE= utf8_bin ROW_FORMAT= Compact;


 /*角色与权限关系表*/
 DROP TABLE IF EXISTS `t_role_permission`; CREATE TABLE `t_role_permission`(`RID` int(11)  NOT NULL COMMENT '角色编号', `PID` int(11)  NOT NULL COMMENT '权限编号', PRIMARY KEY(`RID`, `PID`)  USING BTREE, INDEX `FK_Reference_12`(`PID`)  USING BTREE, CONSTRAINT `FK_Reference_11` FOREIGN KEY(`RID`)  REFERENCES `t_role`(`ID`)  ON
DELETE RESTRICT ON
UPDATE RESTRICT, CONSTRAINT `FK_Reference_12` FOREIGN KEY(`PID`)  REFERENCES `t_permission`(`ID`)  ON
DELETE RESTRICT ON
UPDATE RESTRICT)  ENGINE= InnoDB CHARACTER
   SET= utf8 COLLATE= utf8_general_ci ROW_FORMAT= Compact;


/*用户与角色关系表*/
DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `UID` int(11) NOT NULL COMMENT '用户编号', `RID` int(11) NOT NULL COMMENT '角色编号', PRIMARY KEY (`UID`, `RID`) USING BTREE, INDEX `FK_Reference_10`(`RID`) USING BTREE, CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `t_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `t_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

为了测试,再加一张商品表:

/*商品表*/
DROP TABLE IF EXISTS `t_product`; CREATE TABLE `t_product` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '商品名称', `price` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品价格', `stock` int(11) NULL DEFAULT NULL COMMENT '库存', `is_show` tinyint(4) NULL DEFAULT NULL COMMENT '是否展示', `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;

整合thymeleaf这里不说了,实际项目是前后端分离。

下面正式开始学习security的使用:

一:基本原理:

security就是运用一系列的过滤器,实现不同情况的拦截校验功能。

具体的过滤器这里先不说。

二:认证

认证的意思就是,对所有请求拦截,判断是否登录,如果没有,定位到登录页面。 

security有两种认证方式:HttpBasic认证和form表单认证。 http这种很简陋,就不用说了。 看一下formLogin的认证。

1、表单认证:

controller:

在confifig包下编写SecurityConfiguration配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * http请求方法
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()// 开启表单认证
            .loginPage("/login.html") //认证失败跳转登录页面
            .and().authorizeRequests()
            .antMatchers("/login.html").permitAll() //放行登录页面
            .anyRequest().authenticated(); //所有请求都要认证
    }
}

过滤器UsernamePasswordAuthenticationFilter就是处理表单登录的

 可以看到默认是form表单中的登录url,以及用户名密码输入框的name属性值。是可以修改的:

 假如登录后跳转的是同源页面,页面可能无法展示,需要加载同源域名下iframe页面,需要继续在configure方法中:

对了,还要添加一下登录成功后的跳转路径:

上面的登录认证的用户名密码都是基于框架 ,实际我们要使用数据库的user。需要实现security的一个UserDetailsService接口, 重写这个接口里面 loadUserByUsername即可

SecurityConfifiguration类中指定自定义用户认证

另外,如果密码要使用加密,数据库中也要存密文。

在登录成功后,前端html中可能会需要获取昵称头像等做展示,也就是需要User信息。 如果前端页面也整合了security标签,那就直接获取security框架的UserDetails,因为在登录成功后,框架会存储用户信息。 获取的方式有几种:

2、记住我: remember-me 

 记住我的功能,可以不用每次都输入用户名和密码。

security实现记住我的原理是,在登录的时候,UsernamePasswordAuthenticationFilter认证成功后会调用RememberMeService->TokenRepository,生成一个token存入数据库(关联着用户信息),同时会写到cookie中。 下次登录时候, 会进入RemeberMeAuthticationFilter这个过滤器中,通过cookie从数据库取出该Token对应的用户名以及其他信息放入UserDetailService。

代码实现 :

 第一次启动会自动创建表persistent_logins,并在登录后插入信息:

 但是,记住我功能有风险。 如果cookie被截取,就可以在其他地方利用cookie,访问需要登录后才能访问的接口。 所以要不就不使用该功能,要不就在重要的接口中加入认证:

如果登录是来源于remember-me功能,就报错。

3、验证码 

验证码是为了保证人为操作而非机器,在经过UsernamePasswordAuthenticationFilter登录认证之前,就需要先通过验证码校验,所以可以自己写一个验证码的filter,加入到前面。 

securityConfig中注入并添加

4、session管理 

默认使用Spring的session来管理。

配置文件配置session 超时时间
server.servlet.session.timeout = 60

securityConfig中还可以控制:失效后跳转登录页面; 同一账号同一时间的在线个数(多设备);

 如果在集群环境下,session的管理就应该放到redis等公共的地方实现共享,只需要在配置文件配置:

spring.session.store-type=redis

 7、CSRF(Cross-site request forgery),中文名称:跨站请求伪造

可以这么理解 CSRF 攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。 CSRF 能够做的
事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账 ...... 造成的问题包括:个人隐私泄露以及财产安全。

security中的csrf防御机制:

SpringSecurity 使用 org.springframework.security.web.csrf.CsrfFilter 防御,原理是:
会对所有post请求验证是否包含系统生成的csrf的token信息,如果不包含,则报错。

使用方式: 后端开启,前端也要配置 

 说到csrf, 再说一下跨域。 跨域其实是对浏览器的一种保护,如果产生了跨域,服务器在返回结果时就会被浏览器拦截,导致响应结果不可用。

哪些情况会产生跨域:

 端口号不同,也会跨域。

 security开启跨域支持:

三: 授权:

springSecurity提供了很多内置表达式来控制权限,原理是首先需要告诉security当前的用户有哪些权限,然后在需要鉴权的接口或者其地方,使用表达式来告知,访问当前资源需要什么权限,再判断用户的权限中是否满足。 

 这些表达式具体使用有几种方式:

1、基于url的表达式: 对指定的url做限制

在securityConfig中增加配置,如

如果没有权限会报错,需要给出友情提示,可以自定义提示:

 最后需要在用户登录认证的方法loadUserByUsername(String username)中,给用户添加相关的权限。

2、在表达式中使用bean授权: 

就是写一个bean,在方法中写权限校验逻辑。  然后,还是在securityConfig中指定ur指定使用bean的方法来校验。

import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.Collection;

/**
 * 自定义bean授权
 */
@Component
public class MyAuthorizationService {

    /**
     * 检查用户是否有权限
     *
     * @param authentication 认证信息
     * @param request        请求对象
     * @return
     */
    public boolean check(Authentication authentication, HttpServletRequest request) {
        UserDetails principal = (UserDetails) authentication.getPrincipal();

        String username = principal.getUsername();

        // 获取用户权限的集合
        Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>)
                principal.getAuthorities();

        // 如果用户名为admin 直接返回true
        if ("admin".equalsIgnoreCase(username)) {
            return true;
        } else {
            // 获取请求路径
            String requestURI = request.getRequestURI();
            if (requestURI.contains("/user")) {
                // 循环判断用户的权限集合是否包含ROLE_ADMIN
                for (GrantedAuthority authority : authorities) {
                    if ("ROLE_ADMIN".equals(authority.getAuthority())) {
                        return true;
                    }
                }
            }
            if (requestURI.contains("/product")) {
                // 循环判断用户的权限集合是否包含ROLE_PRODUCT
                for (GrantedAuthority authority : authorities) {
                    if ("ROLE_PRODUCT".equals(authority.getAuthority())) {
                        return true;
                    }
                }
            }
        }
        return false;
    }


    /**
     * 检查ID是否大于10
     *
     * @param authentication 认证信息
     * @param request        请求对象
     * @return
     */
    public boolean check(Authentication authentication, HttpServletRequest request, Integer id) {
        if (id > 10) {
            return false;
        }
        return true;
    }
}

实际上,在项目中,url很多。 如果都在securityConfig类中来配置,一点也不实际。 所以上面两种配置方式大多数情况不太实用。

3、Method安全表达式:

首先,在SecurityConfig配置类上开启方法级别的注解支持

在需要权限控制的接口路径上添加相关的注解:

@PreAuthorize:进入方法前的验证

 

@PostAuthorize:方法执行后再进行权限验证,适合验证带有返回值的权限

@PreFilter : 可以用来对集合类型的参数进行过滤 , 将不符合条件的元素剔除集合

@PostFilter : 可以用来对集合类型的返回值进行过滤 , 将不符合条件的元素剔除集合

 

 上面讲解了几种权限配置认证的方式,说明了什么样的资源需要如何来限制。 原理都是用配置去对比User中的权限是否匹配。

4、RBAC权限控制模型

当然了,抛开上面几种方式来说,其实现在用的最为广泛的,是基于RBAC数据模型(Role-Based Access Control),也就是文章开头准备的5张表,关系如下:

 用户有哪些角色,每个角色可以操作什么权限,都是多对多的关系。

所以:

1、用户登录的时候,就需要根据用户id,去数据库查询所有的权限:

 2、在SecurityConfig配置文件中,项目启动的时候,需要把所有的权限从数据加载到框架中:

 权限信息Permission中,记录了资源url,权限tag(如ROLE_ADMIN),权限名称等信息。这样,框架中有了所有的权限信息,就可以去匹配user中权限的信息了。

当然了,如果没有前后端没有分离,可能还会自己去写前端,前端也可以用security控制。

下一节,看看security源码。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值