初窥SpringSecurity安全框架

初窥SpringSecurity安全框架

作为一名开发怎能不知道大名顶顶的安全框架呢?市面上流行的安全框架有:shiro和springSecurity。那么你经常用哪个框架做安全访问控制呢?因为SpringBoot集成了SpringSecurity,所以我们这次来聊聊它

概念

Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

SpringSecurity官网:官网

对应依赖

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

SpringSecurity包含的模块:

继续,我们可以看到这么一幅图,可以知道它是基于servlet过滤器实现的:

http配置相关:

用户密码验证:

创建项目

1.创建新的项目,选择thymeleaf和SpringWeb依赖
2.新增静态资源(代码省略,主要是各角色页面,需要此部分代码可私我)
3.添加路由控制页面跳转

package com.springstudy.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RouterController {
    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "views/login";
    }

    // 通过restful 实现复用
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id){
        return "views/level1/"+id;
    }

    @RequestMapping("/level2/{id}")
    public String level12(@PathVariable("id") int id){
        return "views/level2/"+id;
    }

    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id){
        return "views/level3/"+id;
    }
}

4.启动项目查看页面效果(默认登陆用户名是user,登陆密码在控制台)

后面我们要实现对应的权限控制效果!

自定义登陆用户和密码

老是这样登不行啊,我么来自定义配置一下登陆用户名和密码

# 清除thymeleaf缓存 这使得我们改动html代码不用重启项目 build一下即可生效
spring.thymeleaf.cache=false
# 用户名
spring.security.user.name=admin
# 密码
spring.security.user.password=123456

新增SecurityConfig配置类

package com.springstudy.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // 可以再这里导入userService相关配置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 不同用户访问内容不同! 这里,过滤器,登录注销规则,安全配置,OAuth2配置
        // 我们平时只需要配置一些基本的规则即可!

        // 首页是允许所有人访问的!
        // 定制授权规则: 那些请求,哪些人可以访问!
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("guest")
                .antMatchers("/level2/**").hasRole("vip")
                .antMatchers("/level3/**").hasRole("svip");

        // 自定义登录页 配置后默认登陆页将失效
        // login跳转到登录页  /login?error 登录失败
        http.formLogin()
                .usernameParameter("username")
                .passwordParameter("password")
                .loginPage("/toLogin")
                .loginProcessingUrl("/login"); // 登陆表单提交请求!

        // 如果注销404,因为 Security 默认是防止 csrf 跨站伪请求!
        // http.csrf().disable(); // 可能会让我们系统不安全

        // 注销 开启默认的注销功能!
        http.logout().logoutSuccessUrl("/"); // 注销成功后跳转至首页!

        // 自定义的登录页需要配置 rememberMe 的参数名,就可以绑定到我们前端的!
        // 记住我功能
        http.rememberMe().rememberMeParameter("remember");
    }

    // 定义用户的认证规则 (角色,密码....)
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 定义user1 uer2 user3 三个用户的角色权限
        // 这里我们一般在用户角色表里存储用户的角色信息
        // 密码没有加密会报500错误,这里我们只定义了用户的密码加密,但是没有定义用户的认证规则的加密方式
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                // 一个人可以拥有多个角色!
                .withUser("user1")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("guest")
                .and()
                .withUser("user2")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("guest","vip")
                .and()
                .withUser("user3")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("guest","vip","svip");
    }
}

修改前台配置

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>首页</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link th:href="@{/test/css/qinstyle.css}" rel="stylesheet">
</head>
<body>

<!--主容器-->
<div class="ui container">

    <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
        <div class="ui secondary menu">
            <a class="item"  th:href="@{/index}">首页</a>
            <!--登录注销-->
            <div class="right menu">
                 <!--核心类:Authentication-->
                <!-- 如果未登录就显示登陆按钮 -->
                <div sec:authorize="!isAuthenticated()">
                    <a class="item" th:href="@{/toLogin}">
                        <i class="address card icon"></i> 登录
                    </a>
                </div>

                <!-- 如果已登录,显示用户的信息 -->
                <div sec:authorize="isAuthenticated()">
                    <a class="item">
                        <!--<i class="address card icon"></i>-->
                        用户名:<span sec:authentication="principal.username"></span> &nbsp;
                        角色:<span sec:authentication="principal.authorities"></span>
                    </a>
                </div>

                <div sec:authorize="isAuthenticated()">
                    <!-- 将注销请求也改成post提交即可! -->
                    <form th:action="@{/logout}" method="post">
<!--                        <button type="submit" class="def-log-out">注销</button>-->
                        <a class="item" th:href="@{/logout}" style="text-decoration:underline;">
                            注销
                        </a>
                    </form>
                </div>
            </div>
        </div>
    </div>

    <div class="ui segment" style="text-align: center">
        <h3>Spring Security Study</h3>
    </div>

    <div>
        <br>
        <div class="ui three column stackable grid">
<!--            <div sec:authorize="hasRole('vip1')">-->
                <div class="column" sec:authorize="hasRole('guest')">
                    <div class="ui raised segment">
                        <div class="ui">
                            <div class="content">
                                <h5 class="content">Level 1</h5>
                                <hr>
                                <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                                <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                                <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                            </div>
                        </div>
                    </div>
                </div>
<!--            <div sec:authorize="hasRole('vip2')">-->
                <div class="column" sec:authorize="hasRole('vip')">
                    <div class="ui raised segment">
                        <div class="ui">
                            <div class="content">
                                <h5 class="content">Level 2</h5>
                                <hr>
                                <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                                <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                                <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                            </div>
                        </div>
                    </div>
                </div>
<!--            <div sec:authorize="hasRole('vip3')">-->
                <div class="column" sec:authorize="hasRole('svip')">
                    <div class="ui raised segment">
                        <div class="ui">
                            <div class="content">
                                <h5 class="content">Level 3</h5>
                                <hr>
                                <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                                <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                                <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                            </div>
                        </div>
                    </div>
                </div>
        </div>
    </div>
</div>
<script th:src="@{/test/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/test/js/semantic.min.js}"></script>
</body>
</html>

重启项目验证


可以看到3个用户,且不同的模块,实现了不同的权限控制。
注:在我们配置注销时,可以看到springSecurity已经帮我们配置好了

当然我们实际的处理方式是不同的角色配置不同的菜单,不会这么控制!这里只是举例说明Security权限控制以及thymeleaf权限控制语法。Sercurity还是登陆鉴权用的多!

登陆页配置记住我

功能:用户没有登录的时候,就只显示导航栏!如果登录了,只显示自己权限能够看到的东西 根据不同的权限,前端展示不同的功能!

springSecrity 和 thymealef 结合:

添加依赖:

<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras- springsecurity5 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId> 
    <artifactId>thymeleaf-extras-springsecurity5</artifactId> 
    <version>3.0.4.RELEASE</version>
</dependency>

前端代码:

<input type="checkbox" name="remember"> 记住我

后端代码:

 // 自定义的登录页需要配置 rememberMe 的参数名,就可以绑定到我们前端的! 
 // 记住我功能 
 http.rememberMe().rememberMeParameter("remember");

 // 定制登陆页
 http.formLogin()
    .usernameParameter("username") // 前端用户名提交参数 
    .passwordParameter("password") // 前端密码提交参数 
    .loginPage("/toLogin") // 登录页面 
    .loginProcessingUrl("/login"); // 登陆表单提交请求!

启动测试看用户信息是否存在cookie,默认过期时间15天:

可以看到对应有个remember-me的cookie,至于这个key为何是remember-me可以去看源码,一切都配置都能去源码中找到答案!删除cookie后刷新页面看看是否自动退出了!

退出的问题

点击退出发现报错了

这是为什么呢?
原来SpringSecurity禁止了get方式的退出,以防止 csrf 跨站伪请求!

// 如果注销404,因为 Security 默认是防止 csrf 跨站伪请求!
// http.csrf().disable(); // 可能会让我们系统不安全

那我们把退出操作改成表单提交的post方式请求即可;

修改index.html注销代码:

<form th:action="@{/logout}" method="post">
    <button type="submit" class="def-log-out">注销</button>
    <!-- <a class="item" th:href="@{/logout}" style="text-decoration:underline;">
        注销
    </a> -->
</form>

build 后再次点击注销可以验证下!

小结:Spring集成Security后,sercurity就变得轻巧了很多,且因为功能强大所以在SpringBoot中面对shrio优势明显,
像我们常见的功能:单点登陆,微信qq登陆认证等它都作了对应的支持!本次只是分享Security的初步使用!