Spring Security

一、Spring Security简介

1.1 Spring Security的模块

不管你想使用Spring Security保护哪种类型的应用程序,第一件需要做的事就是将Spring Security模块添加到应用程序的类路径下。SpringSecurity 3.2分为11个模块。应用程序的类路径下至少要包含Core和Configuration这两个模块。

模板描述
ACL支持通过访问控制列表(access control list,ACL)为域对象提供安全性
切面(Aspects)一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ的切面,而不是使用标准的Spring AOP
CAS客户端(CAS Client)提供与Jasig的中心认证服务(Central Authentication Service,CAS)进行集成的功能
配置(Configuration)包含通过XML和Java配置Spring Security的功能支持
核心(Core)提供Spring Security基本库
加密(Cryptography)提供了加密和密码编码的功能
LDAP支持基于LDAP进行认证
OpenID支持使用OpenID进行集中式认证
Remoting提供了对Spring Remoting的支持
标签库(TagLibrary)Spring Security的JSP标签库
Web提供了Spring Security基于Filter的Web安全性支持

1.2 DelegatingFilterProxy

DelegatingFilterProxy是一个特殊的Servlet Filter,它本身所做的工作并不多。只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个<bean>注册在Spring应用的上下文中。可是使用下面其中一种方式进行配置。
在这里插入图片描述

1)在web.xml中配置

<filter>
 <filter-name>springSecurityFilterChain</filter-name>
 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
 <filter-name>springSecurityFilterChain</filter-name>
 <url-pattern>/</url-pattern>
</filter-mapping>

2)实现AbstractSecurityWebApplicationInitializer类的子类

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class WebSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

1.3 编写简单的安全性配置

1)启用安全功能

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

2) 配置安全性

通过重载WebSecurityConfigurerAdapter中的一个或多个方法来指定安全性。

方法描述
configure(WebSecurity)通过重载,配置Spring Security的Filter链
configure(HttpSecurity)通过重载,配置如何通过拦截器保护请求
configure(AuthenticationManagerBuilder)通过重载,配置user-detail服务
import org.springframework.beans.factory.annotation.Configurable;
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;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
    }
}
二、选择认证用户的方式

Spring Security非常灵活,能够基于各种数据存储来认证用户。它内置了多种常见的用户存储场景,如内存、关系型数据库以及LDAP。但我们也可以编写并插入自定义的用户存储实现。

2.1 使用基于内存的用户存储

因为我们的安全配置类扩展了WebSecurityConfigurerAdapter,因此配置用户存储的最简单方式就是重载configure()方法,并以
AuthenticationManagerBuilder作为传入参数。AuthenticationManagerBuilder有多个方法可以用来配置Spring Security对认证的支持。通过inMemoryAuthentication()方法,我们可以启用、配置并任意填充基于内存的用户存储。

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
                .withUser("admin").password("password").roles("USER", "ADMIN");
    }
}

配置用户信息

除了password()、roles()和and()方法以外,还有其他的几个方法可以用来配置内存用户存储中的用户信息。

方  法描  述
accountExpired(boolean)定义账号是否已经过期
accountLocked(boolean)定义账号是否已经锁定
and()用来连接配置
authorities(GrantedAuthority…)授予某个用户一项或多项权限
authorities(List<? extends GrantedAuthority>)授予某个用户一项或多项权限
authorities(String…)授予某个用户一项或多项权限
credentialsExpired(boolean)定义凭证是否已经过期
disabled(boolean)定义账号是否已被禁用
password(String)定义用户的密码
roles(String…)授予某个用户一项或多项角色

2.2 基于数据库表进行认证

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication().dataSource(dataSource);// dataSource可以根据实际情况创建
    }
}

尽管默认的最少配置能够让一切运转起来,但是它对我们的数据库模式有一些要求。它预期存在某些存储用户数据的表。更具体来说,下面的代码片段来源于Spring Security内部,这块代码展现了当查找用户信息时所执行的SQL查询语句:
在这里插入图片描述
在第一个查询中,我们获取了用户的用户名、密码以及是否启用的信息,这些信息会用来进行用户认证。接下来的查询查找了用户所授予的权限,用来进行鉴权,最后一个查询中,查找了用户作为群组的成员所授予的权限。

如果你能够在数据库中定义和填充满足这些查询的表,那么基本上就不需要你再做什么额外的事情了。但是,也有可能你的数据库与上面所述并不一致,那么你就会希望在查询上有更多的控制权。如果是这样的话,我们可以按照如下的方式配置自己的查询。

改变默认的查询语句

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource).usersByUsernameQuery(
                        "select username, password, true" + 
                         "from TableName where username=?")
                .authoritiesByUsernameQuery("select username, 'ROLE_USER' from TableName where username=?");
        
    }
}

使用转码后的密码

如果数据库中的密码进行了转码的话,那么认证就会失败,因为它与用户提交的明文密码并不匹配。为了解决这个问题,我们需要借助passwordEncoder()方法指定一个密码转码器(encoder):

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.StandardPasswordEncoder;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource).usersByUsernameQuery(
                        "select username, password, true" +
                         "from TableName where username=?")
                .authoritiesByUsernameQuery("select username, 'ROLE_USER' from TableName where username=?")
                .passwordEncoder(new StandardPasswordEncoder("53cr3t"));

    }
}

passwordEncoder()方法可以接受Spring Security中PasswordEncoder接口的任意实现。Spring Security的加密模块包括
了三个这样的实现:BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder。

上述的代码中使用了StandardPasswordEncoder,但是如果内置的实现无法满足需求时,你可以提供自定义的实现,只需要继承PasswordEncoder接口在重写自己的方法即可。

2.3 基于LDAP进行认证

为了让Spring Security使用基于LDAP的认证,我们可以使用ldapAuthentication()方法。这个方法在功能上类似于jdbcAuthentication(),只不过是LDAP版本。如下的configure()方法展现了LDAP认证的简单配置:

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchFilter("(uid={0})")
            .groupSearchFilter("member={0}");
    }
}

userSearchFilter()和groupSearchFilter()用来为基础LDAP查询提供过滤条件,它们分别用于搜索用户和组。默认情况下,对于用户和组的基础查询都是空的,也就是表明搜索会在LDAP层级结构的根开始。但是我们可以通过指定查询基础来改变这个默认行为。

改变默认行为

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}");
    }
}

userSearchBase()属性为查找用户提供了基础查询。同样,groupSearchBase()为查找组指定了基础查询。我们声明用
户应该在名为people的组织单元下搜索而不是从根开始。而组应该在名为groups的组织单元下搜索。

配置密码比对

基于LDAP进行认证的默认策略是进行绑定操作,直接通过LDAP服务器认证用户。另一种可选的方式是进行比对操作。这涉及将输入的密码发送到LDAP目录上,并要求服务器将这个密码和用户的密码进行比对。因为比对是在LDAP服务器内完成的,实际的密码能保持私密。

如果你希望通过密码比对进行认证,可以通过声明passwordCompare()方法来实现。

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}")
            .passwordCompare();
    }
}

默认情况下,在登录表单中提供的密码将会与用户的LDAP条目中的userPassword属性进行比对。如果密码被保存在不同的属性中,可以通过passwordAttribute()方法来声明密码属性的名称:

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.Md4PasswordEncoder;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}")
            .passwordCompare()
            .passwordEncoder(new Md4PasswordEncoder())
            .passwordAttribute("passcode");
    }
}

引用远程的LDAP服务器

默认情况下,Spring Security的LDAP认证假设LDAP服务器监听本机的33389端口。但是,如果你的LDAP服务器在另一台机器上,那么可以使用contextSource()方法来配置这个地址。

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}")
            .contextSource()
            .url("ladp://localhost:389/dc=habuma,dc=com");
    }
}

配置嵌入式的LDAP服务器

如果你没有现成的LDAP服务器供认证使用,Spring Security还为我们提供了嵌入式的LDAP服务器。我们不再需要设置远程LDAP服务器的URL,只需通过root()方法指定嵌入式服务器的根前缀就可以了:

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}")
            .contextSource()
            .root("dc=habuma,dc=com");
    }
}

当LDAP服务器启动时,它会尝试在类路径下寻找LDIF文件来加载数据。LDIF(LDAP Data Interchange Format,LDAP数据交换格式)是以文本文件展现LDAP数据的标准方式。每条记录可以有一行或多行,每项包含一个名值对。记录之间通过空行进行分割。

如果你不想让Spring从整个根路径下搜索LDIF文件的话,那么可以通过调用ldif()方法来明确指定加载哪个LDIF文件。

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.ldapAuthentication()
            .userSearchBase("ou=people")
            .userSearchFilter("(uid={0})")
            .groupSearchBase("ou=groups")
            .groupSearchFilter("member={0}")
            .contextSource()
            .root("dc=habuma,dc=com")
            .ldif("classpath:user.ldif");
    }
}

2.4 配置自定义的用户服务

假设我们需要认证的用户存储在非关系型数据库中,如Mongo或Neo4j,在这种情况下,我们需要提供一个自定义的
UserDetailsService接口实现。

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

我们所需要做的就是实现loadUserByUsername()方法,根据给定的用户名来查找用户。loadUserByUsername()方法会返回代表给定用户的UserDetails对象。如下的程序清单展现了一个UserDetailsService的实现,它会从给定的SpitterRepository实现中查找用户。

实现一个自定义的服务

自定义查找服务的好处是,数据可以来源于任何地方,如关系型数据库、文档数据库或图数据等。

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Collection;

public class MyUserServer implements UserDetailsService {

    /**
     * 根据用户名获取用户信息,权限,角色等
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        
        return null;
    }
}
三、拦截请求

对每个请求进行细粒度安全性控制的关键在于重载configure(HttpSecurity)方法。

3.1 定义拦截规则

import org.springframework.beans.factory.annotation.Configurable;
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 javax.ws.rs.HttpMethod;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/spitters/me").authenticated()
            .antMatchers(HttpMethod.POST, "/spittle").authenticated()
            .anyRequest().permitAll();
    }
}

configure()方法中得到的HttpSecurity对象可以在多个方面配置HTTP的安全性。在这里,我们首先调用authorizeRequests(),然后调用该方法所返回的对象的方法来配置请求级别的安全性细节。其中,第一次调用antMatchers()指定了对“/spitters/me”路径的请求需要进行认证。第二次调用antMatchers()更为具体,说明对“/spittles”路径的HTTP POST请求必须要经过认证。最后对anyRequests()的调用中,说明其他所有的请求都是允许的,不需要认证和任何的权限。antMatchers()方法中设定的路径支持Ant风格的通配符。regexMatchers()方法则能够接受正则表达式来定义请求路径。

authenticated()要求在执行该请求时,必须已经登录了应用。如果用户没有认证的话,Spring Security的Filter将会捕获该请求,并将用户重定向到应用的登录页面。同时,permitAll()方法允许请求没有任何的安全限制。

3.2保护请求的方法

方法能够做什么
access(String)如果给定的SpEL表达式计算结果为true,就允许访问
anonymous()允许匿名用户访问
authenticated()允许认证过的用户访问
denyAll()无条件拒绝所有访问
fullyAuthenticated()如果用户是完整认证的话(不是通过Remember-me功能认证的),就允许访问
hasAnyAuthority(String…)如果用户具备给定权限中的某一个的话,就允许访问
hasAnyRole(String…)如果用户具备给定角色中的某一个的话,就允许访问
hasAuthority(String)如果用户具备给定权限的话,就允许访问
hasIpAddress(String)如果请求来自给定IP地址的话,就允许访问
hasRole(String)如果用户具备给定角色的话,就允许访问
not()对其他访问方法的结果求反
permitAll()无条件允许访问
rememberMe()如果用户是通过Remember-me功能认证的,就允许访问

3.3 使用Spring表达式进行安全保护

如下就是使用SpEL表达式来声明具有“ROLE_SPITTER”角色才能访问“/spitter/me”URL:

.antMatchers("/spitters/me").access("hasRole(ROLE_SPITTER)")

Spring Security通过一些安全性相关的表达式扩展了Spring表达式语言

安全表达式计算结果
authentication用户的认证对象
denyAll结果始终为false
hasAnyRole(list of roles)如果用户被授予了列表中任意的指定角色,结果为true
hasRole(role)如果用户被授予了指定的角色,结果为true
hasIpAddress(IPAddress)如果请求来自指定IP的话,结果为true
isAnonymous()如果当前用户为匿名用户,结果为true
isAuthenticated()如果当前用户进行了认证的话,结果为true
isFullyAuthenticated()如果当前用户进行了完整认证的话(不是通过Remember-me功能进行的认证),结果为true
isRememberMe()如果当前用户是通过Remember-me自动认证的,结果为true
permitAll结果始终为true
principal用户的principal对象

例如,如果你想限制“/spitter/me”URL的访问,不仅需要ROLE_SPITTER,还需要来自指定的IP地址,
那么我们可以按照如下的方式调用access()方法:

.antMatchers("/spitters/me").access("hasRole('ROLE_SPITTER') and hasIpAddress('127.0.0.')")

3.4 强制通道的安全性

传递到configure()方法中的HttpSecurity对象,除了具有authorizeRequests()方法以外,还有一个requiresChannel()方法,借助这个方法能够为各种URL模式声明所要求的通道。

import org.springframework.beans.factory.annotation.Configurable;
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 javax.ws.rs.HttpMethod;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
            .antMatchers("/spitters/me").access("hasRole('ROLE_SPITTER') and hasIpAddress('127.0.0.')")
            .antMatchers(HttpMethod.POST, "/spittle").authenticated()
            .anyRequest().permitAll()
            .and()
            .requiresChannel()
            .antMatchers("/spitters/form").requiresSecure();// 规定此URL需要使用HTTPS
    }
}

不论何时,只要是对“/spitter/form”的请求,Spring Security都视为需要安全通道(通过调用requiresChannel()确定的)并自动将请求重定向到HTTPS上。

与之相反,有些页面并不需要通过HTTPS传送。例如,首页不包含任何敏感信息,因此并不需要通过HTTPS传送。我们可以使
用requiresInsecure()代替requiresSecure()方法,将首页声明为始终通过HTTP传

如果通过HTTPS发送了对“/”的请求,Spring Security将会把请求重定向到不安全的HTTP通道上。
在强制要求通道时,路径的选取方案与authorizeRequests()是相同的。可以使用regexMatchers()方法,通过正则表达式选取路径模式。

四、认证用户

4.1 开启默认登录页

import org.springframework.beans.factory.annotation.Configurable;
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 javax.ws.rs.HttpMethod;

@Configurable
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
             formLogin().and() // 开启默认登录界面,如果没有重写configure方法默认是开启的
            .authorizeRequests()
            .antMatchers("/home/me").authenticated()
            .antMatchers(HttpMethod.POST, "/home").authenticated()
            .anyRequest().permitAll();
    }
}

2)添加自定义的登录界面

创建登录界面login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Hello World!</h1>

<form action="/authentication/form" method="post" name="f">
    用户名:<input type="text" name="username">
    用户密码:<input type="password" name="password">
    <button type="submit" value="login">login</button>
</form>

</body>
</html>

设置自定义登录界面

import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configurable
@EnableWebSecurity
@EnableWebMvc
@ComponentScan
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/hello","/login.jsp").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                //指定登录页的路径
                .loginPage("/hello")
                //指定自定义form表单请求的路径
                .loginProcessingUrl("/authentication/form")
                .failureUrl("/error")
                .defaultSuccessUrl("/success")
                //必须允许所有用户访问我们的登录页(例如未验证的用户,否则验证流程就会进入死循环)
                //这个formLogin().permitAll()方法允许所有用户基于表单登录访问/login这个page。
                .permitAll();

                //默认都会产生一个hiden标签 里面有安全相关的验证 防止请求伪造 这边我们暂时不需要 可禁用掉
                http .csrf().disable();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("user").password(new BCryptPasswordEncoder().encode("123456")).roles("USER").and()
            .withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("USER", "ADMIN").and().passwordEncoder(new BCryptPasswordEncoder());
    } 
}

设置控制器

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HomeController {

    @RequestMapping("/hello")
    public String hello() {
        return "login";
    }

    @RequestMapping("/error")
    public String error() {
        return "error";
    }

    @RequestMapping("/success")
    public String success() {
        return "success";
    }
}

这里值的注意的是表单的用户名name和password输入框的name=""要和security里面的验证的对应:name="username";name="password",否则无法识别,另外action="/authentication/form"要与.loginProcessingUrl("/authentication/form")相对应,原因为: 由于security是由UsernamePasswordAuthenticationFilter这个类定义登录的,里面默认是/login路径,我们要让他用我们的/authentication/form路径,就需要配置.loginProcessingUrl("/authentication/form")

4.2 启用HTTP Basic认证

如果要启用HTTP Basic认证的话,只需在configure()方法所传入的HttpSecurity对象上调用httpBasic()即可。另外,还可以通过调用realmName()方法指定域。

在这里插入图片描述

4.3 启用Remember-me功能

对于应用程序来讲,能够对用户进行认证是非常重要的。但是站在用户的角度来讲,如果应用程序不用每次都提示他们登录是更好的。这就是为什么许多站点提供了Remember-me功能,你只要登录过一次,应用就会记住你,当再次回到应用的时候你就不需要登录了。

Spring Security使得为应用添加Remember-me功能变得非常容易。为了启用这项功能,只需在configure()方法所传入的HttpSecurity对象上调用rememberMe()即可。
在这里插入图片描述
在这里,我们通过一点特殊的配置就可以启用Remember-me功能。默认情况下,这个功能是通过在cookie中存储一个token完成的,这个token最多两周内有效。但是,在这里,我们指定这个token最多四周内有效(2,419,200秒)。
存储在cookie中的token包含用户名、密码、过期时间和一个私钥——在写入cookie前都进行了MD5哈希。默认情况下,私钥的名为SpringSecured,但在这里我们将其设置为spitterKey,使它专门用于Spittr应用。
如此简单。既然Remember-me功能已经启用,我们需要有一种方式来让用户表明他们希望应用程序能够记住他们。为了实现这一点,登录请求必须包含一个名为remember-me的参数。在登录表单中,增加一个简单复选框就可以完成这件事情:
在这里插入图片描述

4.4 退出登录

在应用中,与登录同等重要的功能就是退出。如果你启用Remember-me功能的话,更是如此,否则的话,用户将永远登录在这个系统中。

默认情况下退出功能是通过Servlet容器中的Filter实现的(默认情况下),这个Filter会拦截针对“/logout”的请求。
因此,为应用添加退出功能只需添加如下的链接即可:
<a href="/logout">Logout</a>
当用户点击这个链接的时候,会发起对“/logout”的请求,这个请求会被Spring Security的LogoutFilter所处理。用户会退出应用,所有的Remember-me token都会被清除掉。在退出完成后,用户浏览器将会重定向到“/login?logout”,从而允许用户进行再次登录。

1) 自定义退出后的界面

ogout()提供了配置退出行为的方法。在本例中,调用logoutSuccessUrl()表明在退出成功之后,浏览器需要重定向到“/”。在这里插入图片描述

2)自定义重写默认的LogoutFilter拦截路径

在这里插入图片描述

五、保护视图

1.1 使用Spring Security的JSP标签库

1)在jsp中引入

<%@ taglib prefix="securitt" uri="http://www.springframework.org/security/tags" %>

2) 示例

<securitt:authentication property="principal.username" />

3) 标签

JSP标签作用
security:accesscontrollist如果用户通过访问控制列表授予了指定的权限,那么渲染该标签体中的内容
security:authentication渲染当前用户认证对象的详细信息
security:authorize如果用户被授予了特定的权限或者SpEL表达式的 计算结果为true,那么渲染该标签体中的内容

4)标签属性

认证属性描述
authorities一组用于表示用户所授予权限的GrantedAuthority对象
Credentials用于核实用户的凭证(通常,这会是用户的密码)
details认证的附加信息(IP地址、证件序列号、会话ID等)
principal用户的基本信息对象

示例

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书香水墨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值