doc:https://docs.spring.io/spring-security/site/docs/

基于表单的认证(个性化认证流程):

一、自定义登录页面

1、在securityConfigy配置类中的config方法中添加调用链方法

    @Override
protected void configure(HttpSecurity http) throws Exception { http.formLogin()//指定是表单登录
.loginPage("/cus_login.html")//登录页面
.and()
.authorizeRequests()//授权
.anyRequest()//任何请求
.authenticated();//都需要身份认证
}

2、同时在resources/resources下创建一个html文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body> 自定义登录页面
</body>
</html>

/3、配置好之后,启动项目,访问localhost:9999/h  api

浏览器后报错:,重定向到登录页面次数太多

4、为什么出现这种情况:

  因为在securityconfigy配置类中指定了一个loginPage,但是在方法链中,有表明:任何请求都需要认证

.anyRequest()//任何请求
.authenticated();//都需要身份认证

处理:在anyRequest方法前和authorizeRequests方法后添加antMatchers匹配规则,指定登录页面允许访问

.authorizeRequests()//授权
.antMatchers("/cus_login.html").permitAll()

配置完成后再次访问localhost:9999/h  :,即可跳转至自定义的登录页面

5、完善登录页面

//在usernamePasswordAuthenticationFilter中处理的action为/login,请求方式是post   
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
自定义登录页面
<form action="/authentication/form" method="POST">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="text" name="password"></td>
</tr>
<tr>
<td>
<button type="submit">登录</button>
</td>
</tr>
</table>
</form>
</body>
</html>

因为在登录页面中设定的action为“/authentication/form”,所以在securityConfig配置类的方法链中添加loginProcessingUrl方法

    @Override
protected void configure(HttpSecurity http) throws Exception { http.formLogin()//指定是表单登录
.loginPage("/cus_login.html")//登录页面
.loginProcessingUrl("/authentication/form")
.and()
.authorizeRequests()//授权
.antMatchers("/cus_login.html").permitAll()
.anyRequest()//任何请求
.authenticated();//都需要身份认证
}

重启项目:访问localhost:9999/h

显示为自定义的页面。

csrf().disable();//跨站防护不适用

6、将loginPage(/cus_login.html) html页面改为一个controller代码

①新建一个controller

@RestController
public class LoginSecurityController { @RequestMapping("/authentication/require")
public String requireAuthentication(HttpServletRequest request, HttpServletResponse response) { return null;
}
}

②修改securityconfig配置类中的config中的方法调用链中的loginPage

    @Override
protected void configure(HttpSecurity http) throws Exception { http.formLogin()//指定是表单登录
.loginPage("/authentication/require")//登录页面
.loginProcessingUrl("/authentication/form")
.and()
.authorizeRequests()//授权
.antMatchers("/authentication/require").permitAll()
.anyRequest()//任何请求
.authenticated()//都需要身份认证
.and()
.csrf().disable();//跨站防护不适用
}

二、自定义登录成功的处理

实现AuthenticationSucessHandler接口,一下实现方式是:继承SaveRequestAwareAuthenticationSuccessHandler类(这样的话是可以根据请求方式的类型,来返回不同个数的数据,json或者默认)

package com.nxz.security.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.nxz.security.core.properties.LoginType;
import com.nxz.security.core.properties.SecurityProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; @Component("custAuthenticationSuccessHandler")
@Slf4j
public class CustAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @Autowired//springmvc自动注册的一个mapper类
private ObjectMapper objectMapper; @Autowired
private SecurityProperties securityProperties; @Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
log.info("登录成功"); if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
httpServletResponse.setContentType("application/json;UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication));
} else {
super.onAuthenticationSuccess(httpServletRequest, httpServletResponse, authentication);
} }
}
 @Autowired
private AuthenticationSuccessHandler custAuthenticationSuccessHandler; http.formLogin()
.loginPage("/authentication/require").loginProcessingUrl("/authentication/form")
.successHandler(custAuthenticationSuccessHandler)
.failureHandler(custAuthenticationFailerHandler)
.and()
.authorizeRequests()
.antMatchers("/authentication/require",loginPage,"/code/image").permitAll()
.anyRequest()
.authenticated();

三、自定义登录失败的处理

实现AuthenticationFailureHandler接口,继承SimpleUrlAuthenticationFailureHandler,根据请求方式返回不同的类型

package com.nxz.security.handler;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nxz.security.core.properties.LoginType;
import com.nxz.security.core.properties.SecurityProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException; @Slf4j
@Component("custAuthenticationFailerHandler")
public class CustAuthenticationFailerHandler extends SimpleUrlAuthenticationFailureHandler { @Autowired
private ObjectMapper objectMapper; @Autowired
private SecurityProperties securityProperties; @Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("登录失败!"); if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(JSON.toJSONString(objectMapper.writeValueAsString(exception)));
} else {
super.onAuthenticationFailure(request, response, exception);
} }
}
@Autowired
private AuthenticationFailureHandler custAuthenticationFailerHandler;

四、源码学习

1、认证流程说明

登录请求进来是,会先到UsernamePasswordAuthentication类的,其实最先走的是它的父类AbstractAuthenticationProcessingFilter.java,在父类中会走attemptAuthentication方法,父类没有实现,因此会走子类的方法,当认证成功后,会走到最后successFulAuthentication方法

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {      。。。。
     。。。。
Authentication authResult; try {
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
sessionStrategy.onAuthentication(authResult, request, response);
}
    。。。。
    。。。。
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
} successfulAuthentication(request, response, chain, authResult);
}

attemptAuthentication方法(子类usernamepasswordAuthenticationFilter.java)

    public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
} String username = obtainUsername(request);
String password = obtainPassword(request);
    。。。。
    。。。。
username = username.trim();
    //这个UsernamepasswordAuthenticationToken其实就是封装了username 和password信息
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// 这个setDetails会把请求的一些信息设置到authRequest对象中
setDetails(request, authRequest); return this.getAuthenticationManager().authenticate(authRequest);
}

上边那个this.getAuthenticationManager() 会获取一个authenticationManager类

作用:收集相关的provider,当请求到的时候,循环判断是否支持当前的provider类型

public interface AuthenticationManager {
  //authenticate认证方法,交给实现类实现
Authentication authenticate(Authentication var1) throws AuthenticationException;
}

他的实现类(用的就是ProviderManger类),不同的provider支持的Authentication是不同的

例如:UsernamePasswordAuthenticationToken类型的Authentication时,provider就是DaoAuthenticationProvider,

   public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
Iterator var8 = this.getProviders().iterator(); while(var8.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var8.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
} try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var13) {
this.prepareException(var13, authentication);
throw var13;
} catch (InternalAuthenticationServiceException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
} 。。。。。
}

上边那个标红的provider.authenticate方法会走到DaoAuthenticationProvider对象的父类对象的authticate方法,

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");
});
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false; try {
         //这块会进入子类DaoAuthenticationPrivoder
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} throw var6;
} Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
      
try {
       //校验是否锁定。。(userdetails里边的几个返回值)
this.preAuthenticationChecks.check(user);
       //校验密码是否匹配
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
} cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication)
       this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
     //后置查询 
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
} Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
} return this.createSuccessAuthentication(principalToReturn, authentication, user);
}

子类DapAuthenticationPrivoder类的

    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection(); try {
       //这一块就是调用自定义得人UserDetailsService
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
} else {
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}

进入自定义的CusUserDetailsService

public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
//根据username查找用户信息,在这,先手动写一个user信息
log.info("查找用户信息{}", s); BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String password = encoder.encode("password"); //user前两个参数是进行认证的,第三个参数是当前用户所拥有的权限,security回根据授权代码进行验证
return new User(s, password, true, true, true, true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
// AuthorityUtils.commaSeparatedStringToAuthorityList 这个方法是将一个字符一“,” 分割开
}
}

spring security 学习二的更多相关文章

  1. [转]Spring Security学习总结二

    原文链接: http://www.blogjava.net/redhatlinux/archive/2008/08/20/223148.html http://www.blogjava.net/red ...

  2. SpringBoot + Spring Security 学习笔记(五)实现短信验证码+登录功能

    在 Spring Security 中基于表单的认证模式,默认就是密码帐号登录认证,那么对于短信验证码+登录的方式,Spring Security 没有现成的接口可以使用,所以需要自己的封装一个类似的 ...

  3. [转]Spring Security学习总结一

    [总结-含源码]Spring Security学习总结一(补命名空间配置) Posted on 2008-08-20 10:25 tangtb 阅读(43111) 评论(27)  编辑  收藏 所属分 ...

  4. Spring Security 解析(二) —— 认证过程

    Spring Security 解析(二) -- 认证过程   在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .S ...

  5. Spring security 学习 (自助者,天助之!)

    自己努力,何必要强颜欢笑的求助别人呢?  手心向下不求人! Spring security学习有进展哦: 哈哈! 1.页面都是动态生产的吧! 2.设置权限:  a:pom.xml配置jar包 b:cr ...

  6. SpringBoot + Spring Security 学习笔记(三)实现图片验证码认证

    整体实现逻辑 前端在登录页面时,自动从后台获取最新的验证码图片 服务器接收获取生成验证码请求,生成验证码和对应的图片,图片响应回前端,验证码保存一份到服务器的 session 中 前端用户登录时携带当 ...

  7. Spring Security(二)

    Spring Security(二) 注:凡是源码部分,我已经把英文注释去掉了,有兴趣的同学可以在自己项目里进去看看.:-) 定义用户认证逻辑 用户登录成功后,用户的信息会被 Security 封装在 ...

  8. spring security 学习资料

    spring security 学习资料 网址 Spring Security 文档参考手册中文版 https://springcloud.cc/spring-security.html

  9. Spring Security教程(二):自定义数据库查询

    Spring Security教程(二):自定义数据库查询   Spring Security自带的默认数据库存储用户和权限的数据,但是Spring Security默认提供的表结构太过简单了,其实就 ...

随机推荐

  1. centos7 部署镜像仓库 harbor步骤详解

    一.基础设置  1.1 安装vim.wget yum install -y vim wget  1.2 卸载home.扩大root 如果考虑镜像仓库是给研发团队使用,需要配置较大容量的,因为cento ...

  2. 模仿JQuery封装ajax功能

    需求分析 因为有时候想提高性能,只需要一个ajax函数,不想引入较大的jq文件,尝试过axios,可是get方法不支持多层嵌套的json,post方式后台接收方式似乎要变..也许是我不太会用吧..其实 ...

  3. sqlalchemy防sql注入

    银行对安全性要求高,其中包括基本的mysql防注入,因此,记录下相关使用方法: 注意:sqlalchemy自带sql防注入,但是在 execute执行 手写sql时 需要考虑此安全问题 对于 wher ...

  4. go语言从例子开始之Example29.关闭通道

    关闭 一个通道意味着不能再向这个通道发送值了.这个特性可以用来给这个通道的接收方传达工作已经完成的信息. Example: package main import "fmt" // ...

  5. c++while控制语句

    while语句结构:while(condition){ statement; } condition 表示返回值是true or false 如果返回的一直是true则statement语句则一直执行 ...

  6. MD5文件去重

    //计算文件的MD5码 private string getMD5Hash(string pathName) { string strResult = ""; string str ...

  7. C语言——杂实例

    #include <stdio.h> #include <stdlib.h> #include <string.h> void f (int **p); void ...

  8. js调用浏览器

    定义和用法 open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口. 语法 window.open(URL,name,specs,replace) 参数 说明 URL 可选.打开指定的页面 ...

  9. mysql数据库帐号权限设置

    1.创建帐号 2.给帐号赋权限(xinjinlong帐号只有查看sakila表的权限) 3.更改密码 4.取消授权 revoke all on *.* from sss@localhost ;

  10. jmeter之-非GUI模式&登录实战

    1.执行测试脚本 jmeter -n -t JMeter分布式测试示例.jmx 2.指定结果文件及日志路径 jmeter -n -t JMeter分布式测试示例.jmx -l report\01-re ...