1 开发基于表单的认证

Spring security核心的功能

  • 认证(你是谁?)
  • 授权(你能干什么?)
  • 攻击防护(防止伪造身份)

spring security实现了默认的用户名+密码认证,默认用户名为user,密码为:

spring security基本原理:过滤器链

  对于UsernamePasswordAuthenticationFilter只会拦截 url为/login,method为POST的请求。

1.1 自定义用户认证逻辑

1)处理用户信息获取逻辑

  UserDetailsService接口,只有一个方法:loadUserByUsername

实现该接口:数据库中存放的是加密密码,对于同一个密码不同时间的加密密文不一样

@Component
public class MyUserDetailsService implements UserDetailsService {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
logger.info("用户名信息:" + s);
// 根据用户名查找用户信息
logger.info("数据库密码:" + passwordEncoder.encode("123456"));
// 用户名和密码信息用来做认证,权限信息用来对该用户做授权
return new User(s, "$2a$10$eFw06n0ABK2NFuse8y5f/eDUq7we26qQTceEtXSWNbMXnQ5Yf5Iha",
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}

2)处理用户信息校验逻辑

  处理密码加密解密:在配置文件中将PasswordEncoder对象注入spring容器,等价于@Component+包扫描组件

1.2 个性化用户认证流程

1)对于浏览器,返回自定义登录页面,让UsernamePasswordXxxFilter来处理登录请求;对于调用RESTful服务,返回json错误信息。

   用户登录成功后 ,对于浏览器,返回需要的页面;对于服务,返回json数据。

  权限配置:

    @Override
protected void configure(HttpSecurity http) throws Exception {
http.formLogin()// 表单登录
.loginPage("/authentication/require") //将登录页面重定向到controller
.loginProcessingUrl("/authentication/form")
.and()
.authorizeRequests() //请求授权
.antMatchers("/authentication/require",
securityProperties.getBrowser().getLoginPage()).permitAll()//该页面允许通过
.anyRequest()
.authenticated() // 其他资源需要认证
.and()
.csrf().disable(); // 将跨站防护关掉
}

  

  控制器,根据之前URL的路径判断是否为RESTful服务,在处理

/*
当客户端发出请求,当需要认证时,spring security会重定向到该控制器
*/
@RestController
public class BrowserSecurityController {
private Logger logger = LoggerFactory.getLogger(getClass()); // 请求缓存
private RequestCache requestCache = new HttpSessionRequestCache();
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Autowired
private SecurityProperties securityProperties;
/**
* 当需要身份认证时跳转到这里
* @param request
* @param response
* @return
*/
@RequestMapping("/authentication/require")
@ResponseStatus(code = HttpStatus.UNAUTHORIZED)
public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 判断请求类型,HTML或者app
SavedRequest savedRequest = requestCache.getRequest(request, response);
if(savedRequest!=null){
String targetUrl = savedRequest.getRedirectUrl();
logger.info("引发跳转的URL:"+targetUrl);
// 如果之前的URL为.html结尾的URL,则重定向到登录页面
if(StringUtils.endsWithIgnoreCase(targetUrl, ".html")){
redirectStrategy.sendRedirect(request, response,
securityProperties.getBrowser().getLoginPage());
}
}
return new SimpleResponse("请求的服务需要身份认证,请引导用户到登录页面");
}
}

BrowserSecurityController.java

  在启动项目中的application.properties文件中配置登录页面:

# 配置登录页面
getword.security.browser.loginPage=/demo.html

  读取配置文件信息:

  

import org.springframework.boot.context.properties.ConfigurationProperties;

// 读取前缀为getword.security的属性配置,其中browser中的属性会被读取到browserProperties中
@ConfigurationProperties(prefix = "getword.security")
public class SecurityProperties {
// browser的属性会匹配getword.security.browser后面的属性
private BrowserProperties browser = new BrowserProperties(); public BrowserProperties getBrowser() {
return browser;
} public void setBrowser(BrowserProperties browser) {
this.browser = browser;
}
}

SecurityProperties.java

public class BrowserProperties {
private String loginPage = "/login.html"; //默认值 public String getLoginPage() {
return loginPage;
} public void setLoginPage(String loginPage) {
this.loginPage = loginPage;
}
}

BrowserProperties.java

@Configuration
@EnableConfigurationProperties(SecurityProperties.class) //让属性配置读取器生效
public class SecurityCodeConfig {
}

2)自定义登录成功处理,异步登录,AuthenticationSuccessHandler接口

  自定义登录成处理:

@Component("vstudyAuthenticationSuccessHandler")
public class VstudyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//工具类, 将对象转成json
@Autowired
private ObjectMapper objectMapper;
// 登录成功后调用
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}

VstudyAuthenticationSuccessHandler

  注册,使处理器生效:

3)登录失败处理

@Component("vstudyAuthenticationFailHandler")
public class VstudyAuthenticationFailHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper; private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
logger.info("登录失败");
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); //服务器内部错误
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(e));
}
}

VstudyAuthenticationFailHandler.java

配置:和success类似

4)判断请求方式,做出相应的处理

successHandler:

@Component("vstudyAuthenticationSuccessHandler")
public class VstudyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
//工具类, 将对象转成json
@Autowired
private ObjectMapper objectMapper; @Autowired
private SecurityProperties securityProperties; //获取配置信息 // 登录成功后调用
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}else{
// 调用父类方法,完成重定向跳转
super.onAuthenticationSuccess(request, response, authentication);
}
}
}

VstudyAuthenticationSuccessHandler

failureHandler:

@Component("vstudyAuthenticationFailHandler")
public class VstudyAuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private ObjectMapper objectMapper; private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
logger.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(objectMapper.writeValueAsString(e));
}else{
super.onAuthenticationFailure(request, response, e);
}
}
}

VstudyAuthenticationFailHandler

2 认证流程

3 图形验证码

3.1 生成图形验证码

  验证码图片信息:

public class ImageCode {
private BufferedImage image;
private String code;
private LocalDateTime expireTime;//过期时间
public ImageCode(BufferedImage image, String code, int expireIn){
this.image = image;
this.code = code;
this.expireTime = LocalDateTime.now().plusSeconds(expireIn);
} public ImageCode(BufferedImage image, String code, LocalDateTime expireTime){
this.image = image;
this.code = code;
this.expireTime = expireTime;
}
}

ImageCode

  控制器:

@RestController
public class ValidateCodeController {
public static String SESSION_KEY = "SESSION_KEY_IMAGE_CODE";
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Autowired
private SecurityProperties securityProperties; @GetMapping("/image/code")
public void createCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
ImageCode imageCode = createImageCode(new ServletWebRequest(request,response));
sessionStrategy.setAttribute(new ServletWebRequest(request, response), SESSION_KEY, imageCode);
ImageIO.write(imageCode.getImage(), "JPEG", response.getOutputStream());
} /**
* 生成ImageCode验证码
* @param request
* @return
*/
public ImageCode createImageCode(ServletWebRequest request){
// 生成验证码,方法很多
int width = 60;
int height = 20;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics g = image.getGraphics(); Random random = new Random(); g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
} String sRand = "";
for (int i = 0; i < 4; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
} g.dispose(); return new ImageCode(image, sRand, 100);
}
/**
* 生成随机背景条纹
*
* @param fc
* @param bc
* @return
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}

ValidateCodeController

3.2 验证码校验

自定义过滤器:

public class ValidateCodeFilter extends OncePerRequestFilter {
/**
* 验证码校验失败处理器
*/
private AuthenticationFailureHandler authenticationFailureHandler;
/**
* 系统配置信息
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 系统中的校验码处理器
*/ @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 只处理登录请求
if (StringUtils.equals("/authetication/form", request.getRequestURI())
&& StringUtils.equalsIgnoreCase(request.getMethod(), "POST")) {
try {
logger.info("验证码校验通过");
} catch (ValidateCodeException e) {
//验证失败
authenticationFailureHandler.onAuthenticationFailure(request, response, e);
}
} filterChain.doFilter(request, response);
}
protected void validate(ServletWebRequest request) throws ServletRequestBindingException {
// 从session中拿到imageCode
ImageCode codeInSession = (ImageCode) sessionStrategy.getAttribute(request, ValidateCodeController.SESSION_KEY);
// 获取客户端输入的code,当前请求参数
String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
if(StringUtils.isBlank(codeInRequest)){
throw new ValidateCodeException("验证码不能为空");
}
if(codeInSession==null){
throw new ValidateCodeException("验证码不存在");
}
if(codeInSession.isExpired()){
throw new ValidateCodeException("验证码已过期");
}
if(!StringUtils.equalsIgnoreCase(codeInSession.getCode(), codeInRequest)){
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
} public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
} public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
}

ValidateCodeFilter

配置:

执行流程:

/index.html ->redirect ->/authentication/require(控制器,判断是.html结尾)->login.html ->ValidateCodeFilter ->exception -> VstudyAuthenticationFailHandler ->loginType:JSON

login.html中,使用ajax发送登录请求 -> 验证码过滤器通过 -> UsernamePasswordFilter通过 -> 返回登录结果信息

3.3 验证码的接口

  • 为了方便修改验证码的参数,如宽度、高度、长度等信息,我们将通过配置文件的形式配置这些信息。还有验证码的URL地址。
  • 验证码拦截的接口可配置,比如为了限制用户操作频率,对用户操作使用验证码进行限制。验证码过滤器可以拦截多个控制器请求。
  • 验证码的生成逻辑可以配置

三级配置:

 1)验证码参数:

  默认配置:

/**
* 图形验证码
*/
public class ImageCodeProperties {
private int width = 67;
private int height = 23;
private int len = 4;
private int expireIn = 60; //60秒后过期
//seter getter
}

验证码信息:

/**
* 验证码:包括图形验证码、短信验证码
*/
public class ValidateCodeProperties {
private ImageCodeProperties image;
}

属性读取:

@ConfigurationProperties(prefix = "getword.security")
public class SecurityProperties {
// browser的属性会匹配getword.security.browser后面的属性
private BrowserProperties browser = new BrowserProperties(); // 验证码属性匹配getword.security.code后面的属性
private ValidateCodeProperties code = new ValidateCodeProperties();
}

end

spring security认证的更多相关文章

  1. Spring Security认证配置(三)

    学习本章之前,可以先了解下上篇Spring Security认证配置(二) 本篇想要达到这样几个目的: 1.登录成功处理 2.登录失败处理 3.调用方自定义登录后处理类型 具体配置代码如下: spri ...

  2. spring security 认证源码跟踪

    spring security 认证源码跟踪 ​ 在跟踪认证源码之前,我们先根据官网说明一下security的内部原理,主要是依据一系列的filter来实现,大家可以根据https://docs.sp ...

  3. Spring Security 入门(1-4-2)Spring Security - 认证过程之AuthenticationProvider的扩展补充说明

    1.用户信息从数据库获取 通常我们的用户信息都不会向第一节示例中那样简单的写在配置文件中,而是从其它存储位置获取,比如数据库.根据之前的介绍我们知道用户信息是通过 UserDetailsService ...

  4. Spring Security 入门(1-4-1)Spring Security - 认证过程

    理解时可结合一下这位老兄的文章:http://www.importnew.com/20612.html 1.Spring Security的认证过程 1.1.登录过程 - 如果用户直接访问登录页面 用 ...

  5. Authentication讲解(Spring security认证)

    标准认证过程: 1.用户使用username和password登录 2.系统验证这个password对于该username是正确的 3.假设第二步验证成功,获取该用户的上下文信息(如他的角色列表) 4 ...

  6. Spring Security认证配置(二)

    学习本章之前,可以先了解下上篇Spring Security基本配置. 本篇想要达到这样几个目的: 1.访问调用者服务时,如果是html请求,则跳转到登录页,否则返回401状态码和错误信息 2.调用方 ...

  7. Spring Security认证配置(一)

    学习本章之前,可以先了解下上篇 Spring Security基本配置. 本篇主要讲述Spring Security基于表单,自定义用户认证配置(上篇中的配置,本篇将不再阐述).一共分为三步: 1.处 ...

  8. Authentication(Spring Security 认证笔记)

    这篇文章是对Spring Security的Authentication模块进行一个初步的概念了解,知道它是如何进行用户认证的 考虑一个大家比较熟悉的标准认证过程: 1.用户使用username和pa ...

  9. spring-security-4 (4)spring security 认证和授权原理

    在上一节我们讨论了spring security过滤器的创建和注册原理.请记住springSecurityFilterChain(类型为FilterChainProxy)是实际起作用的过滤器链,Del ...

随机推荐

  1. docker为什么适合devops?

    欢迎访问网易云社区,了解更多网易技术产品运营经验 进阶版结论:Kubernetes + Docker 是 Dev 和 Ops 融合的一个桥梁.   DevOps 强调的是高效组织团队之间如何通过自动化 ...

  2. k8s 入门系列之集群安装篇

    关于kubernetes组件的详解介绍,请阅读上一篇文章<k8s入门系列之介绍篇> Kubernetes集群安装部署 •Kubernetes集群组件: - etcd 一个高可用的K/V键值 ...

  3. Android性能测试-内存

    前言: 近阶段都在探索android性能测试方面的东西,其中一个很重要的指标就是内存.对于内存,主要是一些gc是不是及时,或者说一些引用有没有及时释放,有没有导致oom或者内存持续增加导致卡顿,有没有 ...

  4. P3357 最长k可重线段集问题 网络流

    P3357 最长k可重线段集问题 题目描述 给定平面 x-O-yx−O−y 上 nn 个开线段组成的集合 II,和一个正整数 kk .试设计一个算法,从开线段集合 II 中选取出开线段集合 S\sub ...

  5. sql语句中group by使用

    group by分组函数,group by name 将查询结果按照name进行分组,相同name的记录一组,配合聚合函数,显示每个name的情况.   1,数据源 表A结构如下: CREATE TA ...

  6. 初始linux系统--ubuntu

    ubuntu操作系统  1. Linux系统组成 Linux内核软件程序用于实现CPU和内存分配进程调度设备驱动等核心操作,以面向硬件为主 外围程序面向用户为主,包括分析用户指令的解释器网络服务程序图 ...

  7. leetcode-374-Guess Number Higher or Lower(二分查找)

    题目描述: We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have t ...

  8. (C/C++) 指向函數的指標

    最近再跟指標做朋友, 正好遇到函數與指標. 其實函數也在程式內也是有屬於自己的位址 所以指標一樣能指向函數, 在此釐清自己的觀念以及記錄下來. #include <stdio.h> #in ...

  9. Developer Friendly | 基础设施即代码的事实标准Terraform已支持京东云!

    Developer Friendly | 基础设施即代码的事实标准Terraform已支持京东云! Chef.Puppet.Ansible.SaltStack 都可以称为配置管理工具,这些工具的主要目 ...

  10. 部署一个flask服务记录

    最近使用flask写了一些简单的服务. 服务部署到服务器上进行使用,这个过程会有一些问题,需要进行记录一下. 说明运行的环境情况.使用的是python3.6的虚拟环境,系统是centos7,其他的有u ...