1. 表单登录认证流程分析
官方文档:https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html
AbstractAuthenticationProcessingFilter
用作Filter
验证用户凭据的基础,可以对提交给它的任何身份验证请求进行身份验证。
①发起认证请求,请求中携带用户名、密码,该请求会被UsernamePasswordAuthenticationFilter 拦截, 在 UsernamePasswordAuthenticationFilter 的 attemptAuthentication 方法中将请求中用户名和密码,封装为 Authentication 对象
②将Authentication 对象 交给AuthenticationManager 进行认证;
③认证失败,清除 SecurityContextHodler 以及 记住我中信息,回调AuthenticationFailureHandler 处理;
④认证成功,将认证信息存储到 SecurityContextHodler 及记住我等,回调AuthenticationSuccessHandler 处理;
2. 表单登录认证源码
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 开启请求的权限管理
http.authorizeRequests()
// 放行访问登录页面的/login.html请求
.mvcMatchers("/login.html").permitAll()
// 放行/index请求
.mvcMatchers("/index").permitAll()
// 其他所有的请求都需要去认证
.anyRequest().authenticated()
.and()
// 认证方式为表单认证
.formLogin()
// 指定默认的登录页面
.loginPage("/login.html")
// 指定登录请求路径
.loginProcessingUrl("/doLogin")
// 指定表单用户名的 name 属性为 uname
.usernameParameter("uname")
// 指定表单密码的 name 属性为 passwd
.passwordParameter("passwd")
// 指定登录成功后的自定义处理逻辑
.defaultSuccessUrl("/index")
.and()
// 禁止csrf跨站请求保护
.csrf().disable();
}
}
①进入WebSecurityConfigurer的formLogin方法:
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
/**
* 指定支持基于表单的身份验证。
* 如果未指定 FormLoginConfigurer.loginPage(String),则将生成默认登录页面。
*/
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
}
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
/**
* Creates a new instance
* @see HttpSecurity#formLogin()
*/
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
}
②进入UsernamePasswordAuthenticationFilter类来处理身份验证表单提交:
UsernamePasswordAuthenticationFilter 用来处理身份验证表单提交。在 Spring Security 3.0 之前称为 AuthenticationProcessingFilter。登录表单必须向此过滤器提供两个参数:用户名和密码。要使用的默认参数名称包含在静态字段 SPRING_SECURITY_FORM_USERNAME_KEY
和 SPRING_SECURITY_FORM_PASSWORD_KEY
中。 参数名称也可以通过设置 usernameParameter 和 passwordParameter 属性来更改。 默认情况下,此过滤器响应 URL /login。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER
= new AntPathRequestMatcher("/login","POST");
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
// 从request中获取登录方法,判断是否为post方法
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
// 从request中根据参数名称username获取用户名
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
// 从request中根据参数名称password获取密码
String password = obtainPassword(request);
password = (password != null) ? password : "";
// 将username和password封装成UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
setDetails(request, authRequest);
// 调用AuthenticationManager接口的authenticate方法对UsernamePasswordAuthenticationToken进行认证
// 实际上调用的ProviderManager对象的authenticate方法
return this.getAuthenticationManager().authenticate(authRequest);
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(this.passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(this.usernameParameter);
}
}
③进入authenticate方法内部,发现this.getAuthenticationManager().authenticate(authRequest)
实际调用的是 ProviderManager 类中的 authenticate 方法:
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
// AuthenticationProviders 列表
private List<AuthenticationProvider> providers = Collections.emptyList();
// AuthenticationManager 接口对象
private AuthenticationManager parent;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
int currentPosition = 0;
int size = this.providers.size();
// 遍历AuthenticationProviders列表
for (AuthenticationProvider provider : getProviders()) {
// 判断AuthenticationProvider能否验证当前传递的 Authentication 对象的类型
if (!provider.supports(toTest)) {
continue;
}
try {
// 如果AuthenticationProvider指示它能够验证传递的 Authentication 对象的类型。
// 将尝试使用该 AuthenticationProvider 进行身份验证
result = provider.authenticate(authentication);
}catch (AuthenticationException ex) {
// 如果任何支持 AuthenticationProvider 的身份验证未成功,
// 则最后抛出的 AuthenticationException 将被重新抛出。
lastException = ex;
}
}
if (result == null && this.parent != null) {
// Allow the parent to try.
try {
// 也可以设置父 AuthenticationManager,
// 如果配置的AuthenticationProvider都不能执行身份验证,也会尝试这样做。
parentResult = this.parent.authenticate(authentication);
result = parentResult;
}catch (AuthenticationException ex) {
parentException = ex;
lastException = ex;
}
}
}
}
源码中对该类的说明:
通过 AuthenticationProviders 列表迭代 Authentication 请求。
AuthenticationProvider 通常会按顺序尝试,直到提供非空响应。非空响应表示提供者有权决定身份验证请求,并且不再尝试其他提供者。如果后续提供者成功验证了请求,则忽略之前的验证异常并使用成功的验证。如果没有后续提供者提供非空响应或新的 AuthenticationException,则将使用最后收到的 AuthenticationException。如果没有提供者返回非空响应,或者表明它甚至可以处理身份验证,则 ProviderManager 将抛出 ProviderNotFoundException。也可以设置父 AuthenticationManager,如果配置的AuthenticationProvider都不能执行身份验证,也会尝试这样做。
源码中对 authenticate(Authentication authentication)方法的说明:
尝试对传递的 Authentication 对象进行身份验证。将连续尝试 AuthenticationProvider 列表,直到 AuthenticationProvider 指示它能够验证传递的 Authentication 对象的类型。 然后将尝试使用该 AuthenticationProvider 进行身份验证。如果多个 AuthenticationProvider 支持传递的 Authentication 对象,则第一个能够成功验证 Authentication 对象的人会确定结果,并覆盖早期支持的 AuthenticationProvider 引发的任何可能的 AuthenticationException。 成功认证后,不会尝试后续的 AuthenticationProviders。 如果任何支持 AuthenticationProvider 的身份验证未成功,则最后抛出的 AuthenticationException 将被重新抛出。
断点调用的过程:
遍历AuthenticationProviders列表获得AuthenticationProvider接口的实现类,AuthenticationProvider接口的实现类只有一个AnonymousAuthenticationProvider,它并不能验证Authentication 对象的类型即UsernamePasswordAuthenticationToken 类型,因此跳出循环,循环结束。
跳出for循环后,继续向下走,因为result == null && this.parent != null
条件满足,将继续尝试使用AuthenticationManager中的authenticate方法认证,AuthenticationManager接口的默认实现类是ProviderManager,因此最终调用的仍然是ProviderManager中的authenticate方法,从断点可以看出,父类的ProviderManager中有一个AuthenticationProvider接口的实现类DaoAuthenticationProvider:
断点继续进入authenticate方法内,继续回调ProviderManager中的authenticate方法,遍历AuthenticationProviders列表获得AuthenticationProvider接口的实现类,AuthenticationProvider接口的实现类只有一个DaoAuthenticationProvider,它能够验证Authentication 对象的类型即UsernamePasswordAuthenticationToken 类型,因此将使用DaoAuthenticationProvider的authenticate方法进行身份认证:
④上一步断点可以看出最终调用了DaoAuthenticationProvider的authenticate方法,而DaoAuthenticationProvider继承自AbstractUserDetailsAuthenticationProvider,因此实际调用的是AbstractUserDetailsAuthenticationProvider的authenticate方法。进入AbstractUserDetailsAuthenticationProvider的authenticate方法:
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
// ...
}
public abstract class AbstractUserDetailsAuthenticationProvider
implements AuthenticationProvider, InitializingBean, MessageSourceAware {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 从authentication对象中获取登录用户的用户名
String username = determineUsername(authentication);
boolean cacheWasUsed = true;
// 先从缓存中根据username获取用户详情信息UserDetails
UserDetails user = this.userCache.getUserFromCache(username);
// 如果缓存中获取不到
if (user == null) {
cacheWasUsed = false;
try {
// 到数据源中获取用户详情数据
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}catch (UsernameNotFoundException ex) {
}
// 将获取到的用户数据存入缓存
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
}
// 返回认证成功的Authentication对象
// 子类通常会将用户提供的原始凭据(不是加盐或编码密码)存储在返回的 Authentication 对象中。
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
UsernamePasswordAuthenticationToken result
= new UsernamePasswordAuthenticationToken(principal,
authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
}
⑤断点继续向下走,进入retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication)
方法内部:
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
try {
// 根绝UserDetailsService接口提供的loadUserByUsername方法获取用户信息
UserDetails loadedUser
= this.getUserDetailsService().loadUserByUsername(username);
// ...
return loadedUser;
}
// ...
}
// 获取UserDetailsService
protected UserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
}
UserDetailsService 在整个框架中用作用户 DAO,并且是 DaoAuthenticationProvider 使用的策略:
public interface UserDetailsService {
// 根据用户名获取用户信息
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
因此我们只需要自定义一个类实现UserDetailsService接口,并重写loadUserByUsername方法,就可以实现自定义数据源认证。
3. 三个认证类之间的关系
从上面分析中得知,AuthenticationManager 是认证的核心类,但实际上在底层真正认证时还离不开 ProviderManager 以及 AuthenticationProvider 。他们三者关系是样的呢?
- AuthenticationManager 是一个认证管理器,它定义了 Spring Security 过滤器要执行认证操作。
- ProviderManager AuthenticationManager接口的实现类。Spring Security认证时默认使用就是 ProviderManager。
- AuthenticationProvider 就是针对不同的身份类型执行的具体的身份认证。
AuthenticationManager 与 ProviderManager :
ProviderManager 是 AuthenticationManager 的唯一实现,也是 SpringSecurity 默认使用实现。从这里不难看出默认情况下AuthenticationManager 就是一个ProviderManager。
ProviderManager 与 AuthenticationProvider
AuthenticationManager 与 ProviderManager
ProviderManager 是 AuthenticationManager 的唯一实现,也是 SpringSecurity 默认使用实现。从这里不难看出默认情况下AuthenticationManager 就是一个ProviderManager。ProviderManager 与 AuthenticationProvider
在Spring Seourity 中,允许系统同时支持多种不同的认证方式,例如同时支持用户名/密码认证、ReremberMe 认证、手机号码动态认证等,而不同的认证方式对应了不同的 AuthenticationProvider,所以一个完整的认证流程可能由多个AuthenticationProvider 来提供。
多个AuthenticationProvider 将组成一个列表,这个列表将由ProviderManager 代理。换句话说,在ProviderManager 中存在一个AuthenticationProvider 列表,在Provider Manager 中遍历列表中的每一个AuthenticationProvider 去执行身份认证,最终得到认证结果。
ProviderManager 本身也可以再配置一个 AuthenticationManager 作为parent,这样当ProviderManager 认证失败之后,就可以进入到 parent 中再次进行认证。理论上来说,ProviderManager 的 parent 可以是任意类型的
AuthenticationManager,但是通常都是由ProviderManager 来扮演 parent 的⻆色,也就是 ProviderManager 是ProviderManager 的 parent。
ProviderManager 本身也可以有多个,多个ProviderManager 共用同一个parent。有时,一个应用程序有受保护资源的逻辑组(例如,所有符合路径模式的网络资源,如/api/**),每个组可以有自己的专用 AuthenticationManager。通常,每个组都是一个ProviderManager,它们共享一个父级。然后,父级是一种 全局 资源,作为所有提供者的后备资源。
默认情况下AuthenticationProvider 是由 DaoAuthenticationProvider 类来实现认证的,在DaoAuthenticationProvider 认证时又通过 UserDetailsService 完成数据源的校验。
AuthenticationManager 是认证管理器,在 Spring Security 中有全局AuthenticationManager,也可以有局部AuthenticationManager。全局的AuthenticationManager用来对全局认证进行处理,局部的AuthenticationManager用来对某些特殊资源认证处理。当然无论是全局认证管理器还是局部认证管理器都是由
ProviderManger 进行实现。 每一个ProviderManger中都代理一个AuthenticationProvider的列表,列表中每一个实现代表一种身份认证方式。认证时底层数据源需要调用 UserDetailService 来实现。
4. 配置全局 AuthenticationManager
1、默认的全局 AuthenticationManager
springboot 对 security 进行自动配置时自动在工厂中创建一个全局AuthenticationManager
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
public void initialize(AuthenticationManagerBuilder builder) {
System.out.println(builder);
}
}
默认自动配置创建全局AuthenticationManager,默认找当前项目中是否存在自定义 UserDetailService 实例 自动将当前项目 UserDetailService 实例设置为数据源
默认自动配置创建全局AuthenticationManager,在工厂中使用时直接在代码中注入即可
2、自定义全局 AuthenticationManager
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
return userDetailsManager;
}
// 自定义AuthenticationManager:并没有在工厂中暴露出来
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService());
}
}
一旦通过 configure 方法自定义 AuthenticationManager实现,就会将工厂中自动配置AuthenticationManager 进行覆盖;
一旦通过 configure 方法自定义 AuthenticationManager实现,就需要在实现中指定认证数据源对象 UserDetaiService 实例;
一旦通过 configure 方法自定义 AuthenticationManager实现,这种方式创建AuthenticationManager对象是在工厂内部本地一个 AuthenticationManager对象,不允许在其他自定义组件中进行注入;
3、用来在工厂中暴露自定义AuthenticationManager 实例
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
return userDetailsManager;
}
// 自定义AuthenticationManager:并没有在工厂中暴露出来
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService());
}
// 作用: 用来将自定义AuthenticationManager在工厂中进行暴露,可以在任何位置注入
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}
5. 自定义内存数据源
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();
userDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("admin").build());
return userDetailsManager;
}
// 自定义AuthenticationManager:并没有在工厂中暴露出来
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService());
}
}
6. 自定义数据库数据源
①创建数据库表
CREATE TABLE user
(
id int(11) NOT NULL AUTO_INCREMENT,
username varchar(32) DEFAULT NULL,
password varchar(255) DEFAULT NULL,
enabled tinyint(1) DEFAULT NULL,
accountNonExpired tinyint(1) DEFAULT NULL,
accountNonLocked tinyint(1) DEFAULT NULL,
credentialsNonExpired tinyint(1) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE role
(
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(32) DEFAULT NULL,
name_zh varchar(32) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE user_role
(
id int(11) NOT NULL AUTO_INCREMENT,
uid int(11) DEFAULT NULL,
rid int(11) DEFAULT NULL,
PRIMARY KEY (id),
KEY uid (uid),
KEY rid (rid)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
②插入数据
BEGIN;
INSERT INTO user
VALUES (1, 'root', '{noop}123', 1, 1, 1, 1);
INSERT INTO user
VALUES (2, 'admin', '{noop}123', 1, 1, 1, 1);
INSERT INTO user
VALUES (3, 'blr', '{noop}123', 1, 1, 1, 1);
COMMIT;
BEGIN;
INSERT INTO role
VALUES (1, 'ROLE_product', '商品管理员');
INSERT INTO role
VALUES (2, 'ROLE_admin', '系统管理员');
INSERT INTO role
VALUES (3, 'ROLE_user', '用户管理员');
COMMIT;
BEGIN;
INSERT INTO user_role
VALUES (1, 1, 1);
INSERT INTO user_role
VALUES (2, 1, 2);
INSERT INTO user_role
VALUES (3, 2, 2);
INSERT INTO user_role
VALUES (4, 3, 3);
COMMIT;
③导入数据库依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.15</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
④SpringBoot配置文件
spring.security.user.roles=admin,user
spring.security.user.password=123
spring.security.user.name=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&useSSL=false&serverTimezone=Hongkong
spring.datasource.username=root
spring.datasource.password=root
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.hh.entity
logging.level.com.baizhi=debug
⑤创建 user 对象
@Data
public class User implements UserDetails{
private Integer id;
private String username;
private String password;
private Boolean enabled;
private Boolean accountNonExpired;
private Boolean accountNonLocked;
private Boolean credentialsNonExpired;
private List<Role> roles = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
roles.forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority(role.getName())));
return grantedAuthorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
}
⑥创建 Role 对象
@Data
public class Role {
private Integer id;
private String name;
private String nameZh;
}
⑦创建 UserDao
@Mapper
public interface UserDao {
// "根据用户名查询用户
User loadUserByUsername(String username);
// "根据用户id查询⻆色
List<Role> getRolesByUid(Integer uid);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hh.dao.UserDao">
<select id="loadUserByUsername" resultType="com.hh.entity.User">
select * from user where username ={username}
</select>
<select id="getRolesByUid" resultType="com.hh.entity.Role">
select
r.id,
r.name,
r.name_zh nameZh
from role r, user_role ur
where r.id = ur.rid
and ur.uid ={uid}
</select>
</mapper>
⑧创建 MyUserDetailsService
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userDao.loadUserByUsername(username);
if(Objects.isNull(user)){
throw new RuntimeException("用户不存在");
}
user.setRoles(userDao.getRolesByUid(user.getId()));
return user;
}
}
⑨配置 authenticationManager 使用自定义UserDetailService
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 开启请求的权限管理
http.authorizeRequests()
// 放行访问登录页面的/login.html请求
.mvcMatchers("/login.html").permitAll()
// 放行/index请求
.mvcMatchers("/index").permitAll()
// 其他所有的请求都需要去认证
.anyRequest().authenticated()
.and()
// 认证方式为表单认证
.formLogin()
// 指定默认的登录页面
.loginPage("/login.html")
// 指定登录请求路径
.loginProcessingUrl("/doLogin")
// 指定表单用户名的 name 属性为 uname
.usernameParameter("uname")
// 指定表单密码的 name 属性为 passwd
.passwordParameter("passwd")
// 指定登录成功后的自定义处理逻辑
.defaultSuccessUrl("/index")
.and()
// 禁止csrf跨站请求保护
.csrf().disable();
}
}
启动测试即可。