Spring Security_2
9.4 认证用户
最简单的Spring Security配置的话,那么就能无偿地得到一个登录页。实际上,在重写configure(HttpSecurity)之前,我们都能使用一个简单却功能完备的登录页。但是,一旦重写了configure(HttpSecurity)方法,就失去了这个简单的登录页面。
不过,把这个功能找回来也很容易。我们所需要做的就是在configure(HttpSecurity)方法中,调用formLogin(),如下面的程序清单所示。 请注意,和前面一样,这里调用add()方法来将不同的配置指令连接在一起。 如果我们访问应用的“/login”链接或者导航到需要认证的页面,那么将会在浏览器中展现登录页面。
// 启用默认的登录页
httpSecurity.formLogin()
.and()
.authorizeRequests()
.antMatchers("/spitter/me").hasRole("SPITTER")
.anyRequest().permitAll()
.and()
.requiresChannel()
.antMatchers("/spitter/from")
.requiresSecure();
9.4.1 添加自定义的登录页
需要注意的一个关键点是<form>提交到了什么地方。同时还需要注意username和password输入域,在你的登录页中,需要同样的输入域。最后,假设没有禁用CSRF的话,还需要保证包含了值为CSRF token的“_csrf”输入域。
<input type="hidden" name="_csrf" value="6984_hsd2_wrwqc_wrqd" />
在Thymeleaf模板中,包含了username和password输入域,就像默认的登录页一样,它也提交到了相对于上下文的“/login”页面上。因为这是一个Thymeleaf模板,因此隐藏的“_csrf”域将会自动添加到表单中
<form method="POST" style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0px;">
9.4.2 启用HTTP Basic认证</form>
参考 HTTP Basic 认证 https://blog.csdn.net/yhb241/article/details/80646485
对于应用程序的人类用户来说,基于表单的认证是比较理想的。但是在第16章中,将会看到如何将我们Web应用的页面转化为RESTful API。当应用程序的使用者是另外一个应用程序的话,使用表单来提示登录的方式就不太适合了。
HTTP Basic认证(HTTP Basic Authentication)会直接通过HTTP请求本身,对要访问应用程序的用户进行认证。你可能在以前见过HTTP Basic认证。当在Web浏览器中使用时,它将向用户弹出一个简单的模态对话框。
但这只是Web浏览器的显示方式。本质上,这是一个HTTP 401响应,表明必须要在请求中包含一个用户名和密码。在REST客户端向它使用的服务进行认证的场景中,这种方式比较适合。
如果要启用HTTP Basic认证的话,只需在configure()方法所传入的HttpSecurity对象上调用httpBasic()即可。另外,还可以通过调用realmName()方法指定域。如下是在Spring Security中启用HTTP Basic认证的典型配置:
httpSecurity.formLogin()
.and()
.httpBasic()
.realmName("Spittr")
在httpBasic()方法中,并没有太多的可配置项,甚至不需要什么额外配置。HTTP Basic认证要么开启要么关闭。所以,与其进一步研究这个话题,还不如看看如何通过Remember-me功能实现用户的自动认证。
9.4.3 启用Remember-me功能
对于应用程序来讲,能够对用户进行认证是非常重要的。但是站在用户的角度来讲,如果应用程序不用每次都提示他们登录是更好的。这就是为什么许多站点提供了Remember-me功能,你只要登录过一次,应用就会记住你,当再次回到应用的时候你就不需要登录了。
Spring Security使得为应用添加Remember-me功能变得非常容易。为了启用这项功能,只需在configure()方法所传入的HttpSecurity对象上调用rememberMe()即可。
httpSecurity.formLogin()
.loginPage("/login")
.and()
.rememberMe()
.tokenValiditySeconds(2439800)
.key("spittrKey")
....
在这里,我们通过一点特殊的配置就可以启用Remember-me功能。默认情况下,这个功能是通过在cookie中存储一个token完成的,这个token最多两周内有效。但是,在这里,我们指定这个token最多四周内有效2,419,200秒)。
存储在cookie中的token包含用户名、密码、过期时间和一个私钥——在写入cookie前都进行了MD5哈希。默认情况下,私钥的名为SpringSecured,但在这里我们将其设置为spitterKey,使它专门用于Spittr应用。
如此简单。既然Remember-me功能已经启用,我们需要有一种方式来让用户表明他们希望应用程序能够记住他们。为了实现这一点,登录请求必须包含一个名为remember-me的参数。在登录表单中,增加一个简单复选框就可以完成这件事情:
<input id="remember_me" name="remember-me" type="checkbox"/>
<label for="remember_me" class="inline">Remember me</label>
在应用中,与登录同等重要的功能就是退出。如果你启用Remember-me功能的话,更是如此,否则的话,用户将永远登录在这个系统中。我们下面将看一下如何添加退 出功能。
9.4.4 退出
其实,按照我们的配置,退出功能已经启用了,不需要再做其他的配置了。我们需要的只是一个使用该功能的链接。退出功能是通过Servlet容器中的Filter实现的(默认情况下),这个Filter会拦截针对“/logout”的请求。
因此,为应用添加退出功能只需添加如下的链接即可(如下以Thymeleaf代码片段的形式进行了展现):
<a th:href="@{/logout}">Logout</a>
当用户点击这个链接的时候,会发起对“/logout”的请求,这个请求会被Spring Security的LogoutFilter所处理。用户会退出应用,所有的Remember-me token都会被清除掉。
在退出完成后,用户浏览器将会重定向到“/login?logout”,从而允许用户进行再次登录。
如果你希望用户被重定向到其他的页面,如应用的首页,那么可以在configure()中进行如下的配置
httpSecurity.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/")
logout()提供了配置退出行为的方法。在本例中,调用logoutSuccessUrl()表明在退出成功之后,浏览器需要重定 向到“/”。
除了logoutSuccessUrl()方法以外,你可能还希望重写默认的LogoutFilter拦截路径。我们可以通过调用logoutUrl()方法实现这一功能:
.logout()
.logoutSuccessUrl("/")
.logoutUrl("/signout")
如何在发起请求的时候保护Web应用。这假设安全性主要涉及阻止用户访问没有权限的URL。
但是,如果我们能够不给用户显示其无权访问的连接,那么这也是一个很好的思路。
9.5 保护视图
当为浏览器渲染HTML内容时,你可能希望视图中能够反映安全限制和相关的信息。一个简单的样例就是渲染用户的基本信息(比如显示“您已经以……身份登录”)。或者你想根据用户被授予了什么权限,有条件地渲染特定的视图元素。 在第6章,我们看到了在Spring MVC应用中渲染视图的两个最重要的可选方案:JSP和Thymeleaf。不管你使用哪种方案,都有办法在视图上实现安全性。Spring Security本身提供了一个JSP标签库,而Thymeleaf通过特定的方言实现了与Spring Security的集成。 让我们看一下如何将Spring Security用到视图中,就从Spring Security的JSP标签库开始吧。
9.5.2 使用Thymeleaf的Spring Security方言
Thymeleaf的安全方言提供了条件化渲染和显示认证细节的能力。
为了使用安全方言,我们需要确保Thymeleaf Extras Spring Security已经位于应用的类路径下。 然后,还需要在配置中使用SpringTemplateEngine来注册SpringSecurityDialect。
程序清单9.10所展现的@Bean方法声明了SpringTemplateEngine bean,其中就包含了SpringSecurityDialect。
@Bean
public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
// 注册安全方言
templateEngine.addDialect(new SpringStandardDialect());
return templateEngine;
}
安全方言注册完成之后,我们就可以在Thymeleaf模板中使用它的属性了。首先,需要在使用这些属性的模板中声明安全命名空间:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
...
</html>
标准的Thymeleaf方法依旧与之前一样,使用th前缀,安全方言则设置为使用sec前缀。
这样我们就能在任意合适的地方使用Thymeleaf属性了。比如,假设我们想要为认证用户渲染“Hello”文本。如下的Thymeleaf模板代码片段就能完成这项任务:
<div sec:authorize="isAuthenticated()">
Hello <span sec:authentication="name">someone</span>
</div>
好像没有效果。。。。
9.6 小结
对于许多应用而言,安全性都是非常重要的切面。Spring Security提供了一种简单、灵活且强大的机制来保护我们的应用程序。
借助于一系列Servlet Filter,Spring Security能够控制对Web资源的访问,包括Spring MVC控制器。借助于Spring Security的Java配置模型,我们不必直接处理Filter,能够非常简洁地声明Web安全性功能。
当认证用户时,Spring Security提供了多种选项。我们探讨了如何基于内存用户库、关系型数据库和LDAP目录服务器来配置认证功能。如果这些可选方案无法满足认证需求的话,我们还学习了如何创建和配置自定义的用户服务。
附:参考内容:
-
手工配置springboot + spring security + thymeleaf + thymeleaf-extras-springsecurity https://my.oschina.net/kitos/blog/1632381
-
Spring-Security自定义登录页&inMemoryAuthentication验证 https://www.cnblogs.com/MrSi/p/7993875.html>
-
springboot 构建 security https://docs.spring.io/spring-security/site/docs/5.0.13.BUILD-SNAPSHOT/reference/htmlsingle/
-
初识 Spring Security https://www.w3cschool.cn/springsecurity/
-
参考项目 springboot整合 https://github.com/wean2016/springsecurity
-
JWT的Java使用 https://blog.csdn.net/qq_37636695/article/details/79265711
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
=================================================================
实战代码
基础的自定义页面认证
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
/**
* @description: 继承AbstractSecurityWebApplicationInitializer会自动注册DelegatingFilterProxy
* 等价于xml配置 springSecurityFilterChain
* @version: 1.0
* @data: 2019-04-19 11:52
*/
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
package com.web.spittr.config.security;
import com.web.spittr.data.SpittleRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import javax.sql.DataSource;
@Configuration
@EnableWebMvcSecurity
//@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
SpittleRepository spittleRepository;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 启用默认的登录页
httpSecurity
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/spittle/")
.failureUrl("/spittle/login?error=true")
.and()
.logout()
.logoutSuccessUrl("/login")
.logoutUrl("/spittle/login?logout=true")
.and()
.rememberMe()
.tokenValiditySeconds(2439800)
.key("spittrKey")
.and()
.authorizeRequests()
.antMatchers("/spittle/user/*").hasRole("USER")
.antMatchers("/spittle/admin/*").hasRole("ADMIN")
.anyRequest()
.permitAll()
;
httpSecurity.csrf().disable();
}
/**
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws AuthenticationException {
System.out.println("加载Security。。。读取权限");
// 启用内存用户储存
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("user").password("1").roles("USER").and()
.withUser("admin").password("1").roles("USER","ADMIN");
}
}
<form method="post" action="/login" >
<table>
<tr>
<td>User:</td>
<td> <input name="username" type="text" value="" /> </td>
</tr>
<tr>
<td>Password:</td>
<td> <input name="password" type="password" /> </td>
</tr>
<tr>
<td><input id="remember_me" name="remember-me" type="checkbox"/></td>
<td><label for="remember_me" class="inline">Remember me</label></td>
</tr>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<tr>
<td colspan="2">
<input name="submit" type="submit" value="Login" />
<input name="reset" type="reset" value="Reset" />
</td>
</tr>
</table>
</form>
未完待续
基础的认证虽然完成了,但是没有从数据读取user信息,需要继续来验证