实际开发中,一些应用通常要考虑到安全性问题。例如,对于一些重要的操作,有些请求需要用户验明身份后才可以执行,还有一些请求需要用户具有特定权限才可以执行。这样做的意义,不仅可以用来保护项目安全,还可以控制项目访问效果。
Spring Security介绍针对项目的安全管理,Spring家族提供了安全框架Spring Security,它是一个基于Spring生态圈的,用于提供安全访问控制解决方案的框架。为了方便Spring Boot项目的安全管理,Spring Boot对Spring Security安全框架进行了整合支持,并提供了通用的自动化配置,从而实现了Spring Security安全框架进行了整合支持,并提供了通用的自动化配置,从而实现了Spring Security安全框架中包含的多数安全管理功能
1)MVC Security是Spring Boot整合Spring MVC搭建Web应用的安全管理框架,也是开发中使用最多的一款安全功能。
2)WebFlux Security是Spring Boot整合Spring WebFlux搭建Web应用的安全管理。虽然Spring WebFlux框架刚出现不久、文档不够健全,但是它继承了其他安全功能的优点,后续有可能在Web开发中越来越流行。
3)OAuth2是大型项目的安全管理框架,可以实现第三方认证、单点登录等功能,但是目前Spring Boot版本还不支持OAuth2安全管理框架。
4)Actuator Security用于对项目的一些运行环境提供安全监控,例如Health健康信息、Info运行信息等,它主要作为系统指标供运维人员查看管理系统的运行情况
Spring Security的安全管理有两个重要概念,分别是Authentication(认证)和Authorization(授权)。其中,认证即确认用户是否登录,并对用户登录进行管控;授权即确定用户所拥有的功能权限,并对用户权限进行管控。
基础环境搭建为了更换地使用Spring Boot整合实现MVC Security安全管理功能,实现Authentication(认证)和Authorization(授权)的功能,后续我们将会结合一个访问电影列表和详情的案例进行演示说明,这里先对案例的基础环境进行搭建。
1)创建Spring Boot项目。使用Spring Initializr方式创建一个名为chapter07的Spring Boot项目,在Dependencies依赖选择中选择Web模块中Web依赖以及Template Engines模块中的Thymeleaf依赖,然后根据提示完成羡慕创建。
2)引入页面html资源文件。在项目的resources下templates目录中,引入案例所需的资源文件。
index.html是项目首页面,common和vip文件夹中分别是普通用户和vip用户可访问的页面。
index.html
影视直播厅
欢迎进入电影网站首页
普通电影
index.html首页面中通过标签分类展示了一些普通电影和VIP电影,并且这些电影都通过标签连接到了具体的影片详细路径。
在templates文件夹下,common和vip文件夹中引入的HTML文件就是对应电影的简介信息。
common/1.html
影片详情
返回
飞驰人生
3)编写Web控制层。在chapter07项目中创建名为com.example.chapter07.controller的包,并在该包下创建一个用于页面请求处理的控制类
FileController.java
package com.example.chapter07.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Controller
public class FileController {
@GetMapping("/detail/{type}/{path}")
public String toDetail(@PathVariable("type")String type,
@PathVariable("path")String path){
return "detail/"+type+"/"+path;
}
}
开启安全管理效果测试
在Spring Boot项目中开启Spring Security的方式非常简单,只需要引入spring-boot-statrter-security启动器即可。
添加spring-boot-starter-security启动器
在项目的pom.xml中引入Spring Security安全框架的依赖启动器spring-boot-starter-security
org.springframework.boot spring-boot-starter-security
上述引入的依赖spring-boot-starter-security就是Spring Boot整合Spring Security安全框架而提供的依赖启动器,其版本号由Spring Boot进行统一管理。当在当前Spring Boot版本下,对应的Spring Security框架版本号为5.1.4。
需要说明的是,一旦项目引入spring-boot-start-security启动器,MVC Security和WebFlux Security负责的安全功能都会立即生效;对应OAuth2安全管理功能来说,则还需要额外引入一些其他安全依赖。
项目启动测试
项目启动时会在控制台自动生成一个安全密码(这个密码在每次启动项目时都是随机生成的)。
通过浏览器查看项目首页,
自动跳转到了一个新的登录链接页面"http:/localhost:8080/login",这说明在项目中添加spring-boot-starter-security依赖启动器后,项目实现了Spring Security的自动化配置,并且具有了一些默认的安全管理功能。另外,项目中没有手动创建用户登录页面,而添加了Security依赖后,SPring Boot会自带一个默认的登录页面。
当在Spring Security提供的默认登录页面"/login"中输入错误登录信息后,会重定向到"/login?error"页面并显示出错误信息。
需要说明的是,在Spring Boot项目中加入安全依赖启动器后,Security会默认提供一个可登录的用户信息,其中用户名为user,密码随机生成,这个密码会随着项目的每次启动随机生成并打印在控制台上。
可以发现这种默认安全管理方式存在诸多问题,例如,只有唯一的默认登录用户user、密码随机生成且过于暴露、登录页面及错误提示页面不是我们想要的等。
使用Spring Boot与Spring MVC进行Web开发时,如果项目引入安全依赖启动器,MVC Security安全管理功能就会自动生效,其默认的安全配置是在SecurityAutoConfigration和UserDetailsServiceAutoConfiguration中实现的。其中,Security AutoConfiguration会导入并自动化配置SpringBootWebSecurityConfiguration用于启动Web安全安全管理,UserDetailsServiceAutoConfigration则用于配置用户身份信息。
通过自定义WebSecurityConfigurerAdapter类型的Bean组件,可以完全关闭Security提供的Web应用默认安全配置,但是不会关闭UserDetailsService用户信息自动配置类。如果要关闭UserDetailsService默认用户信息配置,可以之定义UserDetailsService、AuthenticationProvider或AuthenticationManager类型的Bean组件。另外,可以通过自定义WebSecurityConfigurerAdapter类型的Bean组件覆盖默认访问规则。Spring Boot提供了非常多方便的方法,可用于覆盖请求映射和静态资源的访问规则。
WebSecurityConfigurerAdapter类的主要方法与说明
| 方法 | 描述 |
|---|---|
| configure(AuthenticationManagerBuilder auth) | 定制用户认证管理器来实现用户认证 |
| configure(HttpSecurity http) | 定制基于Http请求的用户访问控制 |
通过自定义WebSecurityConfigurerAdapter类型的Bean组件,并重写configure(AuthenticationManagerBuilder auth)方法,可以自定义用户认证。针对自定义用户认证,Spring Security提供了多种自定义认证方法,包括有:In-Memory Authentication(内存身份认证)、Authentication Provider(身份认证提供商)和UserDetailsService(身份详情服务)。
内存身份认证In-Memory Authentication(内存身份认证)是最简单的身份认证方式,主要用于Security安全认证体验和测试。自定义内存身份认证时,只需要在重写的configure(Authentication ManagerBuilder auth)方法中定义测试用户即可。下面通过Spring Boot整合Spring Security实现内存身份认证。
自定义WebSecurityConfigurerAdapter配置类
在chapter07项目中创建名为com.example.chapter07.config的包,并在该包下创建一个配置类SecurityConfig
SecurityConfig.java
package com.example.chapter07.config;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
自定义了一个继承自WebSecurityConfigurerAdapter的SecurityConfig配置类,用于进行MVC Security自定义配置,该类上方的@EnableWebSecurity注解是一个组合注解,其效果等同于@Configuration、@import、@EnableGlobalAuthentication的组合用法,关于这些注解的介绍具体如下:
1)@Configuration注解的作用是将当前自定义的SecurityConfig类作为Spring Boot的配置类。
2)@import注解的作用是根据pom.xml中导入的Web模块和Security模块进行自动化配置
3)@EnableGlobalAuthentication注解则用于开启自定义的全局认证。
需要说明的是,如果是针对Spring WebFlux框架的安全支持,需要在项目中导入Reactive Web模块和Security模块,并使用@EnableWebFluxSecurity注解开启基于WebFlux Security的安全支持。
使用内存进行身份认证
在自定义的SecurityConfig类中重写configure(AuthenticationManagerBuilder auth)方法,并在该方法中使用内存身份认证的方式进行自定义用户认证
SecurityConfig.java
package com.example.chapter07.config;
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.bcrypt.BCryptPasswordEncoder;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth.inMemoryAuthentication().passwordEncoder(encoder)
.withUser("shitou").password(encoder.encode("123456")).roles("common")
.and()
.withUser("李四").password(encoder.encode("123456")).roles("vip");
}
}
重写了WebSecurityConfigurerAdapter类的configure(AuthenticationManagerBuilder auth)方法,并在该方法中使用内存身份认证的方式自定义了认证用户信息。定义用户认证信息时,设置了两个用户,包括用户名、密码和角色。
1)从Spring Security 5开始,自定义用户认证必须设置密码编码器用于保护密码,否则控制台会出现异常错误。
2)Spring Security提供了多种密码编码器,包括BcryptPasswordEncoder、Pbkdf2PasswordEncoder、ScryptPasswordEncoder等,密码设置不限于本例中的BcryptPasswordEncoder密码编码器。
3)自定义用户认证时,可以定义用户角色roles,也可以定义用户权限authorities。在进行赋值时,权限通常是在角色值得基础上添加"ROLE_"前缀。例如,authorities(“ROLE_common”)和roles(“common”)是等效的。
4)自定义用户认证时,可以为某个用户一次指定多个角色或权限,例如roles(“common”,“vip”)或者authorities(“ROLE_common”,“ROLE_vip”)。
效果测试
重启chapter07项目进行效果测试,项目启动成功后,仔细查看控制台打印信息,发现没有默认安全管理时随机生成的密码了。通过浏览器访问"http://localhost:8080"查看首页
实际开发中,用户都是在页面注册和登录时进行认证管理的,而非在程序内部使用内存管理的方式手动控制注册用户,所以上述使用内存身份认证的方式无法用于实际生产,只可以作为初学者的测试使用。
JDBC Authentication(JDBC 身份认证)是通过JDBC连接数据库对已有用户身份进行认证。
数据准备
JDBC身份认证的本质是使用数据库中已有的用户信息在项目中实现用户认证服务,所以需要提前准备好相关数据。这里我们使用之前创建的名为springbootdata的数据库,在该数据库中创建3个表t_customer、t_authority和t_customer_authority,并预先插入几条测试数据。
security.sql
use springbootdata;
drop table if exists `t_customer`;
create table `t_customer`(
`id` int(20) not null auto_increment,
`username` varchar(200) default null,
`password` varchar(200) default null,
`valid` tinyint(1) not null default '1',
primary key (`id`)
) engine=InnoDB auto_increment=4 default charset=utf8;
insert into `t_customer` values('1','shitou','$2a$10$BxzPg9I0VAKfDy6F5SRYhepktsvpfhbdz/iecePudLmMCcOdlK0n6','1');
insert into `t_customer` values('2','李四','$2a$10$BxzPg9I0VAKfDy6F5SRYhepktsvpfhbdz/iecePudLmMCcOdlK0n6','1');
drop table if exists t_authority;
create table `t_authority`(
`id` int(20) not null auto_increment,
`authority` varchar(20) default null,
primary key (`id`)
) engine=InnoDB auto_increment=3 default charset=utf8;
insert into `t_authority` values ('1','ROLE_common');
insert into `t_authority` values ('2','ROLE_vip');
drop table if exists `t_customer_authority`;
create table `t_customer_authority`(
`id` int(20) not null auto_increment,
`customer_id` varchar(20) default null,
`authority_id` varchar(20) default null,
primary key (`id`)
) engine=InnoDB auto_increment=5 default charset=utf8;
insert into `t_customer_authority` values ('1','1','1');
insert into `t_customer_authority` values ('2','2','2');
1)创建用户表t_customer时,用户名username必须唯一,因为Security在进行用户查询时是通过username定位是否存在唯一用户的。
2)创建用户表t_customer时,必须额外定义一个tinyinit类型的字段(对应boolean类型的属性,例如示例中的valid),用于校验用户身份是否合法(默认都是合法的)。
3)初始化用户表t_customer数据时,插入的用户密码password必须是对应编码器编码后的密码,例如示例中的密码就是加密后的形式(对应的原始密码为123456)。因此,在自定义配置类中进行用户密码查询时,必须使用与数据库密码统一的密码编码器进行编码。
4)初始化权限表t_authority数据时,权限authority值必须带有"ROLE_"前缀,而默认的用户角色值则是对应权限值去掉"ROLE_"前缀。
添加JDBC连接数据库的依赖驱动启动器
pom.xml
org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java runtime
进行数据库连接配置
在项目的全局配置文件application.properties中编写对应的数据库连接配置。
application.properties
spring.datasource.url=jdbc:mysq://localhost:3306/springbootdata?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456
使用JDBC进行身份认证
SecurityConfig.java
package com.example.chapter07.config;
import org.springframework.beans.factory.annotation.Autowired;
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.bcrypt.BCryptPasswordEncoder;
import javax.sql.DataSource;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String userSQL="select username,password,valid from t_customer" +
"where username = ?";
String authoritySQL="select c.username,a.authority from t_customer c," +
"t_authority a t_customer_authority ca where" +
"ca.customer_id=c.id and cd.authority_id=a.id and c.username = ?";
auth.jdbcAuthentication().passwordEncoder(encoder)
.dataSource(dataSource)
.usersByUsernameQuery(userSQL)
.authoritiesByUsernameQuery(authoritySQL);
}
}
重写的configure方法中使用JDBC身份认证的方式进行身份认证。使用JDBC身份认证时,首先需要对密码进行编码设置(必须与数据库中用户密码加密方式一致);然后需要加载JDBC进行认证连接的数据源DataSource;最后,执行SQL语句,实现通过用户名username查询用户信息和用户权限。
需要注意的是,定义用户查询的SQL语句时,必须返回用户名username、密码password是否为有效用户valid3个字段信息;定义权限查询的SQL语句时,必须返回用户名username权限authority两个字段信息。否则,登录时输入正确的用户信息会出现PreparedStatement Callback的SQL异常错误信息。
效果测试
重启chapter07项目进行效果测试,项目启动成功后,通过浏览器访问。
对于用户流量较大的项目来说,频繁地使用JDBC进行数据库查询不仅麻烦,而且会降低网站响应速度。对于一个完善地项目来说,如果某些业务已经实现了用户信息查询地服务,就没必要使用JDBC进行身份认证了。
创建数据库实体类
新建包com.example.chapter07.domain
Customer.java
package com.example.chapter07.domain;
import javax.persistence.*;
@Entity(name = "t_customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
private Integer valid;
//省略setter,getter,tostring
}
package com.example.chapter07.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity(name = "t_authority")
public class Authority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String authority;
//省略setter,getter,tostring
}
创建Repository类
CustomerRepository.java
package com.example.chapter07.domain;
import javax.persistence.*;
import java.io.Serializable;
@Entity(name = "t_customer")
public class Customer implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String username;
private String password;
private Integer valid;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getValid() {
return valid;
}
public void setValid(Integer valid) {
this.valid = valid;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", username='" + username + ''' +
", password='" + password + ''' +
", valid=" + valid +
'}';
}
}
AuthorityRepository.java
package com.example.chapter07.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.io.Serializable;
@Entity(name = "t_authority")
public class Authority implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String authority;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getAuthority() {
return authority;
}
public void setAuthority(String authority) {
this.authority = authority;
}
@Override
public String toString() {
return "Authority{" +
"id=" + id +
", authority='" + authority + ''' +
'}';
}
}
定义查询用户及角色信息地服务接口
CustomerService.java
package com.example.chapter07.service;
import com.example.chapter07.domain.Authority;
import com.example.chapter07.domain.Customer;
import com.example.chapter07.repository.AuthorityRepository;
import com.example.chapter07.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CustomerService {
private final CustomerRepository customerRepository;
private final AuthorityRepository authorityRepository;
private final RedisTemplate redisTemplate;
@Autowired
public CustomerService(CustomerRepository customerRepository, AuthorityRepository authorityRepository, RedisTemplate redisTemplate) {
this.customerRepository = customerRepository;
this.authorityRepository = authorityRepository;
this.redisTemplate = redisTemplate;
}
public Customer getCustomer(String username) {
Customer customer = null;
Object o = redisTemplate.opsForValue().get("customer_" + username);
if (o != null) {
customer = (Customer) o;
} else {
customer = customerRepository.findByUsername(username);
if (customer != null) {
redisTemplate.opsForValue().set("customer_" + username, customer);
}
}
return customer;
}
public List getCustomerAuthority(String username) {
List authorities = null;
Object o = redisTemplate.opsForValue().get("authorities_" + username);
if (o != null) {
authorities=(List)o;
}else{
authorities = authorityRepository.getAuthoritiesByUsername(username);
if (authorities.size() > 0) {
redisTemplate.opsForValue().set("authorities_" + username, authorities);
}
}
return authorities;
}
}
定义UserDetailsService用于封装认证用户信息
UserDetailService是Security提供的用于封装认证用户信息的接口,该接口提供的loadUserByUsername(String s)方法用于通过用户名加载用户信息。使用UserDetailsService进行身份认证时,自定义一个UserDetailsService接口的实现类,通过loadUserByUsername(String s)方法封装用户详情信息并返回UserDetails对象供Security认证使用。
UserDetailsServiceImpl.java
package com.example.chapter07.Impl;
import com.example.chapter07.domain.Authority;
import com.example.chapter07.domain.Customer;
import com.example.chapter07.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private final CustomerService customerService;
public UserDetailsServiceImpl(CustomerService customerService) {
this.customerService = customerService;
}
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
Customer customer = customerService.getCustomer(s);
List authorities = customerService.getCustomerAuthority(s);
//对用户权限进行封装
List list=authorities.stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthority()))
.collect(Collectors.toList());
if(customer!=null){
UserDetails userDetails=new User(customer.getUsername(),customer.getPassword(),list);
return userDetails;
}else{
throw new UsernameNotFoundException("当前用户不存在!");
}
}
}
重写了UserDetailsService接口的loadUserByUsername(String s)方法用于借助CustomerService业务处理类获取用户信息和权限信息,并通过UserDetails进行认证用户信息封装。
需要注意的是,CustomerService业务处理类获取User实体类时,必须对当前用户进行非空判断,这里使用throw进行异常处理,如果查询的用户为空,throw会抛出UsernameNotFoundException的异常,如果没有使用throw异常处理,Security将无法识别,导致程序整体报错。
使用UserDetailService进行身份认证
接下来我们在configure(AuthenticationManagerBuilder auth)方法中使用UserDetailsService身份认证的方法进行自定义用户认证。
SecurityConfig.java
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception{
BCryptPasswordEncoder encoder=new BCryptPasswordEncoder();
auth.userDetailsService(userDetailsService).passwordEncoder(encoder);
}
在重写的configure方法中使用UserDetailsService身份认证的方式自定义了认证用户信息。在使用UserDetailsService身份认证时,可直接调用userDetailsService(T userDetailsService)对UserDetailsService实现类进行认证,认证过程中需要对密码进行编码设置。
效果测试
浏览器访问查看效果。
登录成功。
当一个系统建立之后,通常需要适当地做一些权限控制,使得不同用户具有不同的权限操作系统。
自定义用户访问控制实际生产中,网站访问多是基于HTTP请求的,我们已经分析出通过重写WebSecurityConfigurerAdapter类的config(HttpSecurity http)方法可以对基于Http的请求访问进行控制。下面我们通过对configure(HttpSecurity http)方法剖析,分析自定义用户访问控制的实现过程。
configure(HttpSecurity http)方法的参数类型是HttpSecurity类,HttpSecurity类提供了Http请求的限制以及权限、Session管理配置、CSRF跨站请求问题等方法
| 方法 | 描述 |
|---|---|
| authorizeRequests() | 开启基于HttpServietRequest请求访问的限制 |
| formLogin() | 开启基于表单的用户登录 |
| httpBasic() | 开启基于HTTP请求的Basic认证登录 |
| logout() | 开启退出登录的支持 |
| sessionManagement() | 开启Session管理配置 |
| rememberMe() | 开启记住我功能 |
| csrf() | 配置CSRF跨站请求伪造防护功能 |
此处重点讲解用户访问控制,指令先对authorizeRequests()方法的返回值做进一步查看。
| 方法 | 描述 |
|---|---|
| antMatchers(String… antPatterns) | 开启Ant风格的路径匹配 |
| mvcMatchers(String… antPatterns) | 开启MVC风格的路径匹配 |
| regexMatchers(String… regexPatterns) | 开启正则表达式的路径匹配 |
| and() | 功能连接符 |
| anyRequest() | 匹配任何请求 |
| rememberMe() | 开启记住我功能 |
| hasAnyRole(String…roles) | 匹配给定的SpEL表达式计算结果是否为true |
| hasRole(String roles) | 匹配用户是否有某一个角色 |
| hasAnyAuthority(String…authorities) | 匹配用户是否有参数中的任意权限 |
| hasAuthority(String authority) | 匹配用户是否有某一个权限 |
| authenticated() | 匹配已经登录认证的用户 |
| fullyAuthenticated() | 匹配完整登录认证的用户(非rememberMe登录用户) |
| haslpAddress(String ipaddressexpression) | 匹配某IP地址的访问请求 |
| permitAll() | 无条件对请求进行放行 |
自定义用户访问控制
打开之前创建的MVC Security自定义配置类SecurityConfig,重写configure(HttpSecurity http)方法进行用户访问控制。
@Autowired
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/").permitAll()
.antMatchers("/detail/common/**").hasRole("common")
.antMatchers("/detail/vip/**").hasRole("vip")
.anyRequest().authenticated()
.and()
.formLogin();
}
configure()方法设置了用户访问权限,其中,路径为"/“的请求直接放行;路径为”/detail/common/**“的请求,只有用户角色为common(即ROLE_common权限)才允许访问;路径为”/detail/vip/**“的请求,只有用户角色是vip(即ROLE_vip权限)才允许访问;其他请求则要求用户必须先进行登录认证。
效果测试
浏览器访问
直接访问localhost:8080可以进入项目首页,这是因为自定义的用户访问控制中,对”/“的请求是直接放行的,说明自定义用户访问控制生效。
在项目首页单击普通电影或VIP专享电影名称查询电影详情。
在项目首页访问影片详情(实质是请求URL跳转,如”/detail/common/1"),会直接被自定义的访问控制拦截并跳转到默认用户登录界面。在此登录界面输入正确的用户名称和密码信息。
访问common电影
访问vip电影
页面会出现403Forbidden(禁止访问)的错误信息,而控制台不会报错。
注:当前实例没有配置完善的用户注销功能,所以登录一个用户后要切换其他用户的话将浏览器重启,再次使用新账号登录。
自定义用户登录
实际开发中,通常要求定制更美观的用户登录页面,并配置有更好的错误提示信息,此时需要自定义用户登录控制。
formLogin()用户登录方法中涉及用户登录的主要方法及说明
| 方法 | 描述 |
|---|---|
| loginPage(String loginPage) | 用户登录页面跳转路径,默认为get请求的/login |
| sucessForwardUrl(String forwardUrl) | 用户登录成功后的重定向地址 |
| sucessHandler(AuthenticationSuccessHandler successHandler) | 用户登录成功后的处理 |
| defaultSuccessUrl(String defaultSuccessUrl) | 用户直接登录后默认跳转地址 |
| failureForwardUrl(String forwardUrl) | 用户登录失败后的重定向地址 |
| failureUrl(String authenticationFailureUrl) | 用户登录失败后的跳转地址,默认为/login?error |
| failureHandler(AuthenticationFailureHandler authenticationFailureHandler) | 用户登录失败后的错误处理 |
| usernameParameter(String usernameParameter) | 登录用户的用户名参数,默认为username |
| passwordParameter(String passwordParameter) | 登录用户的密码参数,默认为password |
| loginPrecessingUrl(String loginProcessingUrl) | 登录表单提交的路径,默认为post请求的/login |
| permitAll() | 无条件对请求进行放行 |
自定义用户登录页面
login.html
title
通过
普通电影
新增一个
上述修改的用户登录页login.html中,在用户登录的



