zoukankan      html  css  js  c++  java
  • SpringBoot:整合SpringSecurity

    SpringBoot 整合 SpringSecurity;

    用户认证和授权、注销及权限控制、记住我和登录页面定制。

    SpringSecurity(安全)

    搭建环境

    版本: 先使用 SpringBoot 2.2.4.RELEASE

    后面会切换到 SpringBoot 2.0.9.RELEASE

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    
    <!--thymeleaf模板-->
    <dependency>
      <groupId>org.thymeleaf</groupId>
      <artifactId>thymeleaf-spring5</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.thymeleaf.extras</groupId>
      <artifactId>thymeleaf-extras-java8time</artifactId>
    </dependency>
    

    资料下载地址

    1582794735287.png

    编写网页对应的Controller:

    package com.rainszj.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";
        }
    
        @RequestMapping("/level1/{id}")
        public String level1(@PathVariable("id") int id) {
    
            System.out.println(id);
    
            return "views/level1/" + id;
        }
    
        @RequestMapping("/level2/{id}")
        public String level2(@PathVariable("id") int id) {
    
            return "views/level2/" + id;
        }
    
        @RequestMapping("/level3/{id}")
        public String level3(@PathVariable("id") int id) {
    
            return "views/level3/" + id;
        }
    }
    

    测试的时候,关闭 thymeleaf 的缓存

    spring.thymeleaf.cache=false
    

    访问:http://localhost:8080,测试是否可以成功跳转视图!

    1582794838586.png

    简介

    Spring Security 是针对Spring项目的安全框架,也是Spring Boot 底层安全模块默认的技术选型,它可以实现强大的 Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量配置,即可实现强大的安全管理!

    记住几个类:

    • WebSecurityConfigurerAdapter:自定义 Security 策略
    • AuthenticationManagerBuilder:自定义认证策略
    • @EnableWebSecurity:开启 WebSecurity模式

    Spring Security 的两个主要目标是"认证" 和 "授权"(访问控制)

    认证(Authentication )

    授权( Authorization)

    这个概念是通用的,而不是只在 Spring Security 中存在。

    Spring Security官网

    官方文档

    使用

    要使用它只需要在Spring Boot中导入Spring Security 的启动器:

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

    导入依赖后,我们来看看 @EnableWebSecurity这个注解

    @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target(value = { java.lang.annotation.ElementType.TYPE })
    @Documented
    @Import({ WebSecurityConfiguration.class,
          SpringWebMvcImportSelector.class,
          OAuth2ImportSelector.class })
    @EnableGlobalAuthentication
    @Configuration
    public @interface EnableWebSecurity {
    
       /**
        * Controls debugging support for Spring Security. Default is false.
        * @return if true, enables debug support with Spring Security
        */
       boolean debug() default false;
    }
    

    从源码中看出,加了@EnableWebSecurity注解的类本质上也是一个配置类!

    从注释中,可以看到它的用法:

    1582791976362.png

    要使用 Spring Security,只需要 添加 @EnableWebSecurity,并让该类继承WebSecurityConfigurerAdapter,重写其中的 一些configure()方法即可

    用户认证和授权

    我们新建一个文件夹:config,编写一个类:SecurityConfig

    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        // 授权
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            // 链式编程
            // 请求权限的规则
            http.authorizeRequests()
                    .antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3")
                    .and()
                    .formLogin();
    
            // 没有权限默认会到登录页面,需要开启登录的页面
            // 默认会跳到 /login 请求中
            // http.formLogin();
    
        }
    
        /**
         * 认证
         * 需要密码编码:PasswordEncoder
         * 在 SpringSecurity 5.0+ 中新增了很多加密方法
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
            // 这些数据正常应该从数据库中读取
            auth.inMemoryAuthentication()
                    .withUser("rainszj").password("123456").roles("vip2", "vip3")
                    .and()
                    .withUser("root").password("123456").roles("vip1", "vip2", "vip3")
                    .and()
                    .withUser("guest").password("123456").roles("vip1");
        }
    
    }
    

    There is no PasswordEncoder mapped for the id "null"

    必须要对密码进行加密才能使用!

    密码编码:PasswordEncoder

    1582795163737.png

    1582796074825.png

    重写该方法:protected void configure(AuthenticationManagerBuilder auth)

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
        // 这些数据正常应该从数据库中读取
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                .withUser("rainszj").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2", "vip3")
                .and()
                .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1", "vip2", "vip3")
                .and()
                .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
    }
    

    OK!

    关于这些要重写的方法,具体如何操作,我们可以点击WebSecurityConfigurerAdapter这个类中,里面有大量的注释介绍如何重写该方法!

    例如:

    /**
     * Used by the default implementation of {@link #authenticationManager()} to attempt
     * to obtain an {@link AuthenticationManager}. If overridden, the
     * {@link AuthenticationManagerBuilder} should be used to specify the
     * {@link AuthenticationManager}.
     *
     * <p>
     * The {@link #authenticationManagerBean()} method can be used to expose the resulting
     * {@link AuthenticationManager} as a Bean. The {@link #userDetailsServiceBean()} can
     * be used to expose the last populated {@link UserDetailsService} that is created
     * with the {@link AuthenticationManagerBuilder} as a Bean. The
     * {@link UserDetailsService} will also automatically be populated on
     * {@link HttpSecurity#getSharedObject(Class)} for use with other
     * {@link SecurityContextConfigurer} (i.e. RememberMeConfigurer )
     * </p>
     *
     * <p>
     * For example, the following configuration could be used to register in memory
     * authentication that exposes an in memory {@link UserDetailsService}:
     * </p>
     *
     * <pre>
     * &#064;Override
     * protected void configure(AuthenticationManagerBuilder auth) {
     *     auth
     *     // enable in memory based authentication with a user named
     *     // &quot;user&quot; and &quot;admin&quot;
     *     .inMemoryAuthentication().withUser(&quot;user&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;).and()
     *           .withUser(&quot;admin&quot;).password(&quot;password&quot;).roles(&quot;USER&quot;, &quot;ADMIN&quot;);
     * }
     *
     * // Expose the UserDetailsService as a Bean
     * &#064;Bean
     * &#064;Override
     * public UserDetailsService userDetailsServiceBean() throws Exception {
     *     return super.userDetailsServiceBean();
     * }
     *
     * </pre>
     *
     * @param auth the {@link AuthenticationManagerBuilder} to use
     * @throws Exception
     */
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       this.disableLocalConfigureAuthenticationBldr = true;
    }
    

    注销及权限控制

    Semantic UI

    前端添加 注销按钮, http.logout() 默认会跳转到:/logout 请求

    1582798346674.png

    <!--注销-->
    <a class="item" th:href="@{/logout}">
        <i class="sign-out icon"></i> 注销
    </a>
    

    SecurityConfig中配置:

    // 授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
        // 链式编程
        // 请求权限的规则
        http.authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3")
                .and()
                // 没有权限默认会到登录页面,需要开启登录的页面
                // /login
                // http.formLogin();
                .formLogin();
        
    // 简单的可定制化的登出
    // .logout().deleteCookies("remove").invalidateHttpSession(false);
    // .logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success");
    
        // 注销,开启了注销功能,跳到首页,默认会跳到 /login
        http.logout().logoutSuccessUrl("/");
    
    }
    

    访问:http://localhost:8080/login 登录

    点击 注销,会跳转到首页,OK!

    thymeleaf 整合 Spring Secuirty:

    在 Spring Boot 2.2.4.RELEASE 版本中使用:

    <!--thymeleaf Spring Security整合包-->
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        <version>3.0.4.RELEASE</version>
    </dependency>
    

    sec 的命名空间:

    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
    

    在Spring Boot 2.0.9.RELEASE 版本中使用:

    <!--thymeleaf Spring Security整合包-->
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        <version>3.0.4.RELEASE</version>
    </dependency>
    

    sec 的命名空间:

    xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
    

    现在切换到:SpringBoot 2.0.9.RELEASE

    引入 thymeleaf-extras-springsecurity4 版本的命名空间后,IDEA会有提示,但引入thymeleaf-extras-springsecurity5 后,没有提示,很奇怪!

    在index.html 中做如下修改:

    <div class="right menu">
      <!--如果未登录,显示登录-->
      <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">
          用户名:<span sec:authentication="name"></span>
          &nbsp;&nbsp;
          角色:<span sec:authentication="principal.authorities"></span>
        </a>
      </div>
    
      <div sec:authorize="isAuthenticated()">
        <!--注销-->
        <a class="item" th:href="@{/logout}">
          <i class="sign-out icon"></i> 注销
        </a>
      </div>
    

    目前了解到的有关SpringSecurity的标签属性有以下几个:

    • sec:authorize="isAuthenticated()"
      判断用户是否已经登陆认证,引号内的参数必须是isAuthenticated()
    • sec:authentication=“name”
      获得当前用户的用户名,引号内的参数必须是name
    • sec:authorize=“hasRole(‘role’)”
      判断当前用户是否拥有指定的权限。引号内的参数为权限的名称。
    • sec:authentication="principal.authorities"
      获得当前用户的全部角色,引号内的参数必须是principal.authorities

    1582804639651.png

    注意:注销失败可能的原因

    在 SpringBoot 2.2.4.RELEASE 中,点击注销功能时,有个确认按钮,内部使用了post方法,而 SpringBoot 2.0.9.RELEASE 中没有提示按钮,默认使用了get方式,get 请求不安全,明文传输,为防止网站攻击,Spring Boot默认会开启csrf(跨站请求伪造),想要注销成功,需要在授权功能中关闭 csrf 这个功能!

    // 授权
    @Override
    protected void configure(HttpSecurity http) throws Exception {
      	// ......
      
      	// 关闭 csrf 功能
      	http.csrf().disable();
    }
    
    <!--菜单根据用户的角色动态实现-->
    <div class="column" sec:authorize="hasRole('vip1')">
        <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 class="column" sec:authorize="hasRole('vip2')">
        <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 class="column" sec:authorize="hasRole('vip3')">
        <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>
    

    1582806234029.png

    登录:http://localhost:8080/login
    1582806263748.png

    1582806198691.png

    1582806219114.png

    记住我及登录页面定制

    如果不适用记住我功能,当我们在浏览器端输入用户名和密码登录,关闭浏览器时,会话结束,再次打开浏览器访问时,需要再次输入用户名和密码登录!

    而记住我功能,本质上是一个 Cookie,而Spring Security 的记住我功能使用了Session和Cookie。

    • [ ] 记住我功能:

    默认自带的

    要使用Spring Security默认的登录表单实现 记住我 功能,只需在授权方法(configure(HttpSecurity http))中,添加一行代码即可:

    // 开启记住我功能,cookie默认保存两周
    http.rememberMe();
    

    1582807965050.png

    1582808127798.png

    自定义

    要使用自定义的表单实现记住我功能:

    前端:

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

    在原有remeberMe()方法中指定前端的 name 参数即可,名称可以任意写:

    // 开启记住我功能,cookie默认保存两周,需要自定义接收前端的参数
    http.rememberMe().rememberMeParameter("remberme");
    

    1582808944187.png

    1582808966518.png

    登录页面定制:

    开启默认登录页面

    要使用Spring Security 默认的登录页面,在授权方法中添加一行代码即可!

    // 没有权限默认会到登录页面,需要开启登录的页面
    // 会默认跳转到 /login 请求
    http.formLogin();
    

    自定义登录页面

    方式一

    同样在 configure(HttpSecurity http),授权方法中添加如下,但是 loginPage() 的请求要和表单提交的 action 请求地址一致!

    http.formLogin().loginPage("/toLogin");
    

    前端登录的 form 表单:

    <form th:action="@{/toLogin}" method="post">
        <div class="field">
            <label>Username</label>
            <div class="ui left icon input">
                <input type="text" placeholder="Username" name="username">
                <i class="user icon"></i>
            </div>
        </div>
        <div class="field">
            <label>Password</label>
            <div class="ui left icon input">
                <input type="password" name="password">
                <i class="lock icon"></i>
            </div>
        </div>
        <div class="field">
            <input type="checkbox" name="remberme"> 记住我
        </div>
    
        <input type="submit" class="ui blue submit button"/>
    </form>
    

    方式二

    需要加上实际登录时处理的URL:loginProcessingUrl("/login");

    http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login");
    

    前端:

    <form th:action="@{/login}" method="post">
        <div class="field">
            <label>Username</label>
            <div class="ui left icon input">
                <input type="text" placeholder="Username" name="username">
                <i class="user icon"></i>
            </div>
        </div>
        <div class="field">
            <label>Password</label>
            <div class="ui left icon input">
                <input type="password" name="password">
                <i class="lock icon"></i>
            </div>
        </div>
        <div class="field">
            <input type="checkbox" name="remberme"> 记住我
        </div>
    
        <input type="submit" class="ui blue submit button"/>
    </form>
    

    这里的表单参数能够提交成功,是因为在formLogin()的源码中,默认的用户名和密码的name参数默认是:username 和 password

    1582809792570.png

    假设我们前端提交参数名不是 username 和 password 的呢?

    现在将用户名改为:name 密码改为:pwd,再试试,发现提交失败!,走到了 error 请求

    1582809930622.png

    那么该如何修改呢?通过看 formLogin() 方法的注释我们也能得知,只需指定提交的参数名即可

    http.formLogin()
            .loginPage("/toLogin")
            .loginProcessingUrl("/login")
            .usernameParameter("name")
            .passwordParameter("pwd");
    

    测试即可,搞定!

  • 相关阅读:
    软件工程第四次作业
    软件工程第三次作业
    软件工程第二次作业
    软件工程第一次作业
    软件工程最后一次作业
    软件工程第四次作业
    软件工程第二次作业
    软件工程最后一次作业
    软件工程第二次结对作业
    软件工程第三次作业
  • 原文地址:https://www.cnblogs.com/rainszj/p/12730718.html
Copyright © 2011-2022 走看看