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 请求。)
那解决方法无非三种。
- 将 **csrf防御关掉,**这样会有安全隐患(除了方便没有优点
// 关闭 csrf防御
http.csrf().disable;
-
使 logout请求方式为 post,这就办法很多了
- 使用 form标签
<form th:action="@{/logout}" method="post"><input class="item" type="submit" value="注销"></input></form>
-
写点击事件 使用 Ajax让 a标签请求方式为 post
方法很多,让请求方式 为post 又不是什么难事对吧
-
使用 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();
方法的源码注释中能够得出一些信息
* @Override
* protected void configure(HttpSecurity http) throws Exception {
* http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
* .usernameParameter("username") // default is username
* .passwordParameter("password") // default is password
* .loginPage("/authentication/login") // default is /login with an HTTP get
* .failureUrl("/authentication/login?failed") // default is /login?error
* .loginProcessingUrl("/authentication/login/process"); // default is /login
* // with an HTTP
* // post
* }
:此处的 &quto转义字符为 英文的引号 即 一> "
.loginPage("/authentication/login") // default is /login with an HTTP get
默认的 登录页面路径为 /login
.loginProcessingUrl("/authentication/login/process"); 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()来对接一下