Java进阶之路Spring

Spring Security_2

2019-04-25  本文已影响5人  elijah777

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目录服务器来配置认证功能。如果这些可选方案无法满足认证需求的话,我们还学习了如何创建和配置自定义的用户服务。

附:参考内容:

  1. 手工配置springboot + spring security + thymeleaf + thymeleaf-extras-springsecurity https://my.oschina.net/kitos/blog/1632381

  2. Spring-Security自定义登录页&inMemoryAuthentication验证 https://www.cnblogs.com/MrSi/p/7993875.html>

  3. springboot 构建 security https://docs.spring.io/spring-security/site/docs/5.0.13.BUILD-SNAPSHOT/reference/htmlsingle/

  4. 初识 Spring Security https://www.w3cschool.cn/springsecurity/

  5. 参考项目 springboot整合 https://github.com/wean2016/springsecurity

  6. 记住账号 https://www.jianshu.com/p/6537deeac03d

  7. 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信息,需要继续来验证

上一篇下一篇

猜你喜欢

热点阅读