spring security 学习二
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 学习二的更多相关文章
- [转]Spring Security学习总结二
原文链接: http://www.blogjava.net/redhatlinux/archive/2008/08/20/223148.html http://www.blogjava.net/red ...
- SpringBoot + Spring Security 学习笔记(五)实现短信验证码+登录功能
在 Spring Security 中基于表单的认证模式,默认就是密码帐号登录认证,那么对于短信验证码+登录的方式,Spring Security 没有现成的接口可以使用,所以需要自己的封装一个类似的 ...
- [转]Spring Security学习总结一
[总结-含源码]Spring Security学习总结一(补命名空间配置) Posted on 2008-08-20 10:25 tangtb 阅读(43111) 评论(27) 编辑 收藏 所属分 ...
- Spring Security 解析(二) —— 认证过程
Spring Security 解析(二) -- 认证过程 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security .S ...
- Spring security 学习 (自助者,天助之!)
自己努力,何必要强颜欢笑的求助别人呢? 手心向下不求人! Spring security学习有进展哦: 哈哈! 1.页面都是动态生产的吧! 2.设置权限: a:pom.xml配置jar包 b:cr ...
- SpringBoot + Spring Security 学习笔记(三)实现图片验证码认证
整体实现逻辑 前端在登录页面时,自动从后台获取最新的验证码图片 服务器接收获取生成验证码请求,生成验证码和对应的图片,图片响应回前端,验证码保存一份到服务器的 session 中 前端用户登录时携带当 ...
- Spring Security(二)
Spring Security(二) 注:凡是源码部分,我已经把英文注释去掉了,有兴趣的同学可以在自己项目里进去看看.:-) 定义用户认证逻辑 用户登录成功后,用户的信息会被 Security 封装在 ...
- spring security 学习资料
spring security 学习资料 网址 Spring Security 文档参考手册中文版 https://springcloud.cc/spring-security.html
- Spring Security教程(二):自定义数据库查询
Spring Security教程(二):自定义数据库查询 Spring Security自带的默认数据库存储用户和权限的数据,但是Spring Security默认提供的表结构太过简单了,其实就 ...
随机推荐
- centos7 部署镜像仓库 harbor步骤详解
一.基础设置 1.1 安装vim.wget yum install -y vim wget 1.2 卸载home.扩大root 如果考虑镜像仓库是给研发团队使用,需要配置较大容量的,因为cento ...
- 模仿JQuery封装ajax功能
需求分析 因为有时候想提高性能,只需要一个ajax函数,不想引入较大的jq文件,尝试过axios,可是get方法不支持多层嵌套的json,post方式后台接收方式似乎要变..也许是我不太会用吧..其实 ...
- sqlalchemy防sql注入
银行对安全性要求高,其中包括基本的mysql防注入,因此,记录下相关使用方法: 注意:sqlalchemy自带sql防注入,但是在 execute执行 手写sql时 需要考虑此安全问题 对于 wher ...
- go语言从例子开始之Example29.关闭通道
关闭 一个通道意味着不能再向这个通道发送值了.这个特性可以用来给这个通道的接收方传达工作已经完成的信息. Example: package main import "fmt" // ...
- c++while控制语句
while语句结构:while(condition){ statement; } condition 表示返回值是true or false 如果返回的一直是true则statement语句则一直执行 ...
- MD5文件去重
//计算文件的MD5码 private string getMD5Hash(string pathName) { string strResult = ""; string str ...
- C语言——杂实例
#include <stdio.h> #include <stdlib.h> #include <string.h> void f (int **p); void ...
- js调用浏览器
定义和用法 open() 方法用于打开一个新的浏览器窗口或查找一个已命名的窗口. 语法 window.open(URL,name,specs,replace) 参数 说明 URL 可选.打开指定的页面 ...
- mysql数据库帐号权限设置
1.创建帐号 2.给帐号赋权限(xinjinlong帐号只有查看sakila表的权限) 3.更改密码 4.取消授权 revoke all on *.* from sss@localhost ;
- jmeter之-非GUI模式&登录实战
1.执行测试脚本 jmeter -n -t JMeter分布式测试示例.jmx 2.指定结果文件及日志路径 jmeter -n -t JMeter分布式测试示例.jmx -l report\01-re ...
