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");
    

    测试即可,搞定!

  • 相关阅读:
    204. Count Primes (Integer)
    203. Remove Linked List Elements (List)
    202. Happy Number (INT)
    201. Bitwise AND of Numbers Range (Bit)
    200. Number of Islands (Graph)
    199. Binary Tree Right Side View (Tree, Stack)
    198. House Robber(Array; DP)
    191. Number of 1 Bits (Int; Bit)
    190. Reverse Bits (Int; Bit)
    189. Rotate Array(Array)
  • 原文地址:https://www.cnblogs.com/rainszj/p/12730718.html
Copyright © 2011-2022 走看看