SpringBoot学习(七)SpringSecurity功能补充

222 阅读7分钟

SpringSecurity

在上一篇文章中,我们通过重写 SpringSecurity中的 config方法实现了授权和认证的功能。

但这只是 SpringSecurity的冰山一角,我们再看看 SpringSecurity的一些其他好玩的功能。

个人建议还是将上一篇文章看一看,为了防止看不懂 demo

文章地址

注销

在上一篇文章的 SpringSecurity demo中,我们如果想要切换用户的话 可以再访问一次 /login页面。

但这种方式显然是有问题的,正常的话应该有一个注销的功能供我们使用。

SpringSecurity就提供了这样一个功能,和配置登录页面跳转一样 极其简单。

// 配置注销
http.logout();

好像默认也是开着的,直接访问 /logout就能跳转到注销页面。当然写还是要写的毕竟要对它进行配置

为了图方便我们可以写一个按钮或者 a标签 通过点击的方式跳转到 /logout页面

可以看到,注销成功的话会让我们再次登录。

我们可以通过配置使其注销成功后跳转到指定的页面

// 配置注销功能,注销成功后跳转到首页
http.logout().logoutSuccessUrl("/");

重启服务器 你会发现注销成功不会再让你登录了 而是跳转到首页。

注销报 404问题

这是 SpringSecurity注销功能 和 csrf防御一起使用的问题,默认情况下 SpringSecurity是打开了 csrf防御的。

Adding CSRF will update the LogoutFilter to only use HTTP POST. This ensures that log out requires a CSRF token and that a malicious user cannot forcibly log out your users.

(译:添加CSRF将更新LogoutFilter以仅使用HTTP POST。这样可以确保log out 请求需要CSRF令牌并且恶意用户无法伪造你的log out 请求。)

那解决方法无非三种。

  1. csrf防御关掉这样会有安全隐患(除了方便没有优点
// 关闭 csrf防御
http.csrf().disable;
  1. 使 logout请求方式为 post,这就办法很多了

    1. 使用 form标签
    <form th:action="@{/logout}" method="post"><input class="item" type="submit" value="注销"></input></form>
    
    1. 写点击事件 使用 Ajax让 a标签请求方式为 post

      方法很多,让请求方式 为post 又不是什么难事对吧

  2. 使用 logoutRequestMatcher(); 方法,显式的设置 /logout请求为GET

    这样的话就可以在不关掉 csrf防御的同时 使用get请求让注销成功了

http.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout","GET"));

我对安全研究不多,你觉得哪种最稳妥用哪种就好

整合 thymeleaf

像上面这种程度的 demo仍不完善,用户未登录的话也会显示 "注销" 按钮。

正常的话,应该在用户登录后 才会显示该按钮。我们应该在用户未登录之前隐藏 "注销" 按钮才是

想实现这种功能,我们通过 thymeleaf 的 th:if 应该也可以,但 SpringSecurity是可以和 thymeleaf进行整合的。

我们来了解一下具体整合

整合 thymeleaf之前需要导入依赖,如果你在创建 SpringBoot项目时将 SpringSecurity和 thymeleaf都勾选了

默认是会将该整合依赖导进来的:

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

导入依赖后,我们在 index.html中添加命名空间

命名空间导入后可能存在一些问题,比如出现写代码不提示的情况 第一次接触的话 还蛮头疼的

怎么说呢,因为 thymeleaf的兼容性大家也都清楚,总是会出一些奇奇怪怪的问题。如果报错就试试降低版本吧 我是指 SpringBoot

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

th:if类似,SpringSecurity整合 thymeleaf后,我们可以通过 sec:authorize="isAuthenticated"

来判断用户是否登录,也就是说我们可以将一些 登录信息和注销按钮写在该标签下

<!--登陆后显示用户名-->
<div  sec:authorize="isAuthenticated" class="item" style="padding-top: 15px;">
   <i class="user icon"></i>
    <!--通过 sec:authentication 可以拿到我们想要的登录信息-->
    <span sec:authentication="name"></span>
</div>
<!--登录后才显示注销按钮及用户名-->
<div sec:authorize="isAuthenticated">
    <!--注销-->
    <a class="item" th:href="@{/logout}" style="padding-top: 15px;">
        <i class="sign-out icon"></i> <span>注销</span>
    </a>
</div>

上述代码有使用 semantic-ui来美化 html页面,感兴趣可以去他们官网看一看。

正常情况下,登录按钮 应该只会在未登录时才会显示,登录后则消失。

而实现该功能,我们只需要一个取反的操作,即 sec:authorize="!isAuthenticated"

在 "isAuthenticated" 前面加上一个 "!" 表示取反(只能用英文符号这种问题应该不需要解释吧)。

<!-- 如果未登录显示登录按钮-->
<div sec:authorize="!isAuthenticated">
    <!--登录-->
    <a class="item" th:href="@{/login}" style="padding-top: 15px;">
        <i class="address card icon"></i> 登录
    </a>
</div>

看看效果:

可以了,基本实现了我们想要的全部功能。

我们再锦上添花一下,将用户无权访问的 路径隐藏

该操作很简单,sec:authorize=""除了能判断用户是否已登录,还能够通过 hasRole()方法来判断该用户的权限

比如说:

<!--我们在 负责level2文件夹中 html文件跳转的 div标签上 来判断用户的权限是否为 logged,也就是判断是否为非游客-->
<div class="column" sec:authorize="hasRole('logged')">
<!-- level3,同上-->
<div class="ui raised segment" sec:authorize="hasRole('admin')">

写上权限判断后,我们看看效果如何。

RememberMe

正常网站的登录页应该不会像 SpringSecurity提供的登陆页面一般。

应该都会存在 一个 remember me功能,也就是将用户的 登录信息保存到 cookie里。

SpringSecurity自然也考虑到了这一点,我们可以通过书写以下代码配置该功能。

// 配置记住我功能
http.rememberMe();

写完这行代码后,SpringSecurity提供的登录页面中就会多出一个 Remeber me让我们勾选

通过查看 cookie我们能够看到该 登录信息保存的时间为两个星期(如果服务器不关的话)

在这两个星期内,如果客户端不将该 cookie清掉 登录信息理论上是能够保存的

自定义登录页

可能有些会写前端登录页的朋友,或者有特定需求的朋友会觉得 SpringSecurity提供的 登录页太丑了......

没办法将就的 使用这种比较简洁的且不能扩展功能的登陆页。

那登录页的话,可以换成我们自定义的吗? 答案是可以的,毕竟 SpringSecurity这么大一框架。

我们从 formLogin();方法的源码注释中能够得出一些信息

*   &#064;Override
*  protected void configure(HttpSecurity http) throws Exception {
*     http.authorizeRequests().antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;).and().formLogin()
*           .usernameParameter(&quot;username&quot;) // default is username
*           .passwordParameter(&quot;password&quot;) // default is password
*           .loginPage(&quot;/authentication/login&quot;) // default is /login with an HTTP get
*           .failureUrl(&quot;/authentication/login?failed&quot;) // default is /login?error
*           .loginProcessingUrl(&quot;/authentication/login/process&quot;); // default is /login
*                                                     // with an HTTP
*                                                     // post
*  }

:此处的 &quto转义字符为 英文的引号 即 --> "

.loginPage(&quot;/authentication/login&quot;) // default is /login with an HTTP get

默认的 登录页面路径为 /login

.loginProcessingUrl(&quot;/authentication/login/process&quot;); default is /login with an HTTP post

登录信息提交页面路径也是 /login,提交的的方式为 post

很常见的 RestFul风格路径,即 get方式请求的 /login为登录页 post方式请求则是提交页。

所以在写 登录页面跳转时尽量避免 使用 @PostController注解


我们可以通过以上两个方法,来对登录页路径、信息提交页路径进行一些自定义配置

// 配置 SpringSecurity登录跳转路径为 自定义路径 "/toLogin"
http.formLogin().loginPage("/toLogin");

写一个Controller 将请求("/toLogin")和 html文件映射一下,需要注意的是 不要用 @PostController注解。

// 该注解默认请求方式为 get
@RequestMapping({"/toLogin"})
public String toLogin(){
    return "views/login";
}

将原先首页绑定的 a标签 thymeleaf表达式 th:href@{"/login"}修改为th:href@{"/toLogin"}

登录信息提交路径可以默认不配置,即 在登录页 html文件中将表单信息以 post方式提交,提交路径与 配置的登录页路径一致就好。

// 提交方式为 post 提交路径与配置的 登录页路径一致
<form th:action="@{/toLogin}" method="post">

当然了,如果你想配置也是可以的 方式也很简单。

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

配置后对应的也需要 在 html文件中修改提交路径

// 将提交路径修改为 配置后的路径
<form th:action="@{/doLogin}" method="post">

重启服务器,现在就可以使用自己写的 html登录页了。

现在还有一件事要做,将 remember me功能拿到我们自定义的登录框。

<input type="checkbox" name="remember" th:text="记住我">
// 配置 remember me
http.rememberMe().rememberMeParameter("remember");

如果碰到登录参数不一致的问题,可以使用 http.formLogin().username/passwordParameter()来对接一下


放松一下眼睛

原图P站地址

画师主页