参考视频,编程不良人

由前面的学习可以知道,SS的默认的拦截规则很简单,我们在项目中实际使用的时候往往需要更加复杂的拦截规则,这个时候就需要自定义一些拦截规则。

自定义拦截规则

在我们的项目中,资源往往是需要不同的权限才能操作的,可以分为下面几种:

  • 公共资源:可以随意访问
  • 认证访问:只有登录了之后的用户才能访问。
  • 授权访问:登录的用户必须具有响应的权限才能够访问。

我们想要自定义认证逻辑,就需要创建一些原来不存在的bean,这个时候就可以使@ConditionalOnMissingBean注解发现创建默认的实现类失效。

测试环境搭建

 @RequestMapping("/public/test")public String justatest(){return "just a test,这个是公共资源!";}@RequestMapping("/private/t1")public String t1(){return "访问受限资源!";}

下面我们重写一个配置类去替换内部默认的配置类

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {//ss里面要求放行的资源要写在任何请求的前面http.authorizeRequests()//开启请求的权限管理.mvcMatchers("/public/**").permitAll().anyRequest().authenticated().and().formLogin();//表单验证的方式}
}

下面测试,访问公共资源

访问/private/t1跳转到

输入账号密码之后访问到

自定义登录界面

在前面的学习中我们知道了默认的登录界面是在过滤器DefaultLoginPageGeneratingFilter里面实现的,现在我们想要自定义一个登录界面。

  • 首先引入thymeleaf依赖
     <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
  • 在templates目录下面创建一个login的html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>冬木自定义用户登录</title></head>
<body><form th:action="@{/login}" method="post">用户名:<input type="text" name="username"><br>密码:<input type="text" name="password"><br><input type="submit" name="登录"></form>
</body>
</html>

编写一个controller接口用于跳转到我们自己写的登录页面,

这里的前缀默认就是在templates下面因此我下面直接return login

package com.dongmu.controller;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;@Controller
public class LoginController {@RequestMapping("/login.html")public String login(){return "login";}
}

添加配置路径,

spring:thymeleaf:cache: false #可以让我们的修改立即生效

另外把认证相关的接口放行

 @Overrideprotected void configure(HttpSecurity http) throws Exception {//ss里面要求放行的资源要写在任何请求的前面http.authorizeRequests()//开启请求的权限管理.mvcMatchers("/public/**").permitAll().mvcMatchers("/login.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login");//表单验证的方式,同时指定默认的登录界面}

这个时候再去访问页面就会跳转到下面这个页面

这个时候登录会发现还是条状到登录页面,这里要注意,一旦指自定义了登录页面就需要指定登录的url,所以我们在接口里面添加下面的代码

//ss里面要求放行的资源要写在任何请求的前面http.authorizeRequests()//开启请求的权限管理.mvcMatchers("/public/**").permitAll().mvcMatchers("/login.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html")//表单验证的方式,同时指定默认的登录界面//一旦自定义登录界面必须指定登录url.loginProcessingUrl("/login").and().csrf().disable();

这个时候就可以登录成功了。

但是这时候要注意源码中指定了登录的参数名,只能是username和password。

这个时候可以进行修改如下

http.authorizeRequests()//开启请求的权限管理.mvcMatchers("/public/**").permitAll().mvcMatchers("/login.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html")//表单验证的方式,同时指定默认的登录界面//一旦自定义登录界面必须指定登录url.loginProcessingUrl("/login").usernameParameter("uname")//指定登录的参数.passwordParameter("pwd")
//                .successForwardUrl("")//默认验证成功之后的跳转,这个是请求转发, 登录成功之后//直接跳转到这个指定的地址,原来的地址不跳转了。.defaultSuccessUrl("")//这个也是成功之后的跳转路径,默认是请求重定向。 登录成功之//后会记住原来访问的路径,也可以再传递一个boolean参数指定地址默认false.and().csrf().disable();

前后端分离项目路径跳转

前面介绍了前后端不分离项目的登录认证成功之后的路径跳转,但是针对于前后端分离项目,比如有的时候可能会发送AJAX请求,这个时候怎么处理呢?


我们可以自定义一个类实现AuthenticationSuccessHandler接口即可。

package com.dongmu.config;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {HashMap<String,Object> hashMap = new HashMap<>();hashMap.put("msg","登录成功");hashMap.put("code",200);hashMap.put("auth",authentication);response.setContentType("application/json;charset=utf-8");String s = new ObjectMapper().writeValueAsString(hashMap);response.getWriter().write(s);}
}

在successHandler里面配置即可

//ss里面要求放行的资源要写在任何请求的前面http.authorizeRequests()//开启请求的权限管理.mvcMatchers("/public/**").permitAll().mvcMatchers("/login.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html")//表单验证的方式,同时指定默认的登录界面//一旦自定义登录界面必须指定登录url.loginProcessingUrl("/login")
//                .usernameParameter("uname")
//                .passwordParameter("pwd")
//                .successForwardUrl("")//默认验证成功之后的跳转,这个是请求转发, 登录成功之后直接跳转到这个指定的地址,原来的地址不跳转了。
//                .defaultSuccessUrl("")//这个也是成功之后的跳转路径,默认是请求重定向。 登录成功之后会记住原来访问的路径.successHandler(new MyAuthenticationSuccessHandler())//前后端分离的处理方案.and().csrf().disable();

这个时候登录成功返回的是一个json字符串。

身份验证失败跳转

首先点进



UsernamePasswordAuthenticationFilter这个类里面由一个方法attemptAuthentication进行身份的验证

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);if (username == null) {username = "";}if (password == null) {password = "";}username = username.trim();UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}

然后最后一句代码authenticate(authRequest)会进入

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();for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}try {result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException | InternalAuthenticationServiceException e) {prepareException(e, authentication);// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow e;} catch (AuthenticationException e) {lastException = e;}}if (result == null && parent != null) {// Allow the parent to try.try {result = parentResult = parent.authenticate(authentication);}catch (ProviderNotFoundException e) {// ignore as we will throw below if no other exception occurred prior to// calling parent and the parent// may throw ProviderNotFound even though a provider in the child already// handled the request}catch (AuthenticationException e) {lastException = parentException = e;}}if (result != null) {if (eraseCredentialsAfterAuthentication&& (result instanceof CredentialsContainer)) {// Authentication is complete. Remove credentials and other secret data// from authentication((CredentialsContainer) result).eraseCredentials();}// If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published itif (parentResult == null) {eventPublisher.publishAuthenticationSuccess(result);}return result;}// Parent was null, or didn't authenticate (or throw an exception).if (lastException == null) {lastException = new ProviderNotFoundException(messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() },"No AuthenticationProvider found for {0}"));}// If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published itif (parentException == null) {prepareException(lastException, authentication);}throw lastException;}

上面代码中

try {result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}

这一块会进入

public Authentication authenticate(Authentication authentication)throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports","Only UsernamePasswordAuthenticationToken is supported"));// Determine usernameString username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);}

这里面user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);的实现

protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;}catch (InternalAuthenticationServiceException ex) {throw ex;}catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}

可以发现这里就是去一开始我们学习的map里面找到对应用户名和密码,这里面应该会报出异常。这个异常后面会被这个方法接收

private void doAuthenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {Authentication authResult;Object principal = getPreAuthenticatedPrincipal(request);Object credentials = getPreAuthenticatedCredentials(request);if (principal == null) {if (logger.isDebugEnabled()) {logger.debug("No pre-authenticated principal found in request");}return;}if (logger.isDebugEnabled()) {logger.debug("preAuthenticatedPrincipal = " + principal+ ", trying to authenticate");}try {PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken(principal, credentials);authRequest.setDetails(authenticationDetailsSource.buildDetails(request));authResult = authenticationManager.authenticate(authRequest);successfulAuthentication(request, response, authResult);}catch (AuthenticationException failed) {unsuccessfulAuthentication(request, response, failed);if (!continueFilterChainOnUnsuccessfulAuthentication) {throw failed;}}}

执行unsuccessfulAuthentication

protected void unsuccessfulAuthentication(HttpServletRequest request,HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {SecurityContextHolder.clearContext();if (logger.isDebugEnabled()) {logger.debug("Cleared security context due to exception", failed);}//这里会把异常信息放到request作用域当中request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, failed);if (authenticationFailureHandler != null) {authenticationFailureHandler.onAuthenticationFailure(request, response, failed);}}

这里配置请求转发

protected void configure(HttpSecurity http) throws Exception {//ss里面要求放行的资源要写在任何请求的前面http.authorizeRequests()//开启请求的权限管理.mvcMatchers("/public/**").permitAll().mvcMatchers("/login.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html")//表单验证的方式,同时指定默认的登录界面//一旦自定义登录界面必须指定登录url.loginProcessingUrl("/login")
//                .usernameParameter("uname")
//                .passwordParameter("pwd")
//                .successForwardUrl("")//默认验证成功之后的跳转,这个是请求转发, 登录成功之后直接跳转到这个指定的地址,原来的地址不跳转了。
//                .defaultSuccessUrl("")//这个也是成功之后的跳转路径,默认是请求重定向。 登录成功之后会记住原来访问的路径.successHandler(new MyAuthenticationSuccessHandler())//前后端分离的处理方案.failureForwardUrl("/login.html")//登录失败之后的请求转发页面
//                .failureUrl("/login.html")//登录失败之后的重定向页面.and().csrf().disable();}

可以直接从request作用域中获取异常

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>冬木自定义用户登录</title></head>
<h2><div th:text="${SPRING_SECURITY_LAST_EXCEPTION}"></div>
</h2>
<body><form th:action="@{/login}" method="post">用户名:<input type="text" name="username"><br>密码:<input type="text" name="password"><br><input type="submit" name="登录"></form>
</body>
</html>


如果是在重定向就会放在session作用域中。如果是请求转发就会放到reques作用域中。

前后端分离项目认证失败处理

实现接口AuthenticationFailureHandler

package com.dongmu.config;import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;public class MyAuthenticationHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {HashMap<String,Object> hashMap = new HashMap<>();hashMap.put("msg","登录成功");hashMap.put("code",200);hashMap.put("auth",authentication);response.setContentType("application/json;charset=utf-8");String s = new ObjectMapper().writeValueAsString(hashMap);response.getWriter().write(s);}@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {HashMap<String,Object> hashMap = new HashMap<>();hashMap.put("code",403);hashMap.put("msg",exception.getMessage());response.setContentType("application/json;charset=utf-8");String s = new ObjectMapper().writeValueAsString(hashMap);response.getWriter().write(s);}
}

配置认证失败接口实现类

protected void configure(HttpSecurity http) throws Exception {//ss里面要求放行的资源要写在任何请求的前面http.authorizeRequests()//开启请求的权限管理.mvcMatchers("/public/**").permitAll().mvcMatchers("/login.html").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html")//表单验证的方式,同时指定默认的登录界面//一旦自定义登录界面必须指定登录url.loginProcessingUrl("/login")
//                .usernameParameter("uname")
//                .passwordParameter("pwd")
//                .successForwardUrl("")//默认验证成功之后的跳转,这个是请求转发, 登录成功之后直接跳转到这个指定的地址,原来的地址不跳转了。
//                .defaultSuccessUrl("")//这个也是成功之后的跳转路径,默认是请求重定向。 登录成功之后会记住原来访问的路径
//                .successHandler(new MyAuthenticationSuccessHandler())//前后端分离的处理方案
//                .failureForwardUrl("/login.html")//登录失败之后的请求转发页面.failureUrl("/login.html")//登录失败之后的重定向页面.failureHandler(new MyAuthenticationHandler()).and().csrf().disable();}

SpringSecurity学习笔记(三)自定义资源拦截规则以及登录界面跳转相关推荐

  1. element ui登录界面_vue vue-router vuex element-ui axios 写一个代理平台的学习笔记(四)美化头部和登录界面...

    美化头部header.vue 找张头像当用户头像 在写点css来美化一下头部,完善一下现在能完成的功能 header.vue :default-active="$route.path&quo ...

  2. Spring框架学习笔记(三)(AOP,事务管理)

    Spring框架学习笔记(三) 九.AOP 9.1 AOP的注解配置 (1) 新建计算器核心功能(模拟:不能在改动核心代码) (2) 建立一个普通的Java类写增强代码(面向切面编程),使用Sprin ...

  3. J2EE学习笔记三:EJB基础概念和知识 收藏

    J2EE学习笔记三:EJB基础概念和知识 收藏 EJB正是J2EE的旗舰技术,因此俺直接跳到这一章来了,前面的几章都是讲Servlet和JSP以及JDBC的,俺都懂一些.那么EJB和通常我们所说的Ja ...

  4. K8S 学习笔记三 核心技术 Helm nfs prometheus grafana 高可用集群部署 容器部署流程

    K8S 学习笔记三 核心技术 2.13 Helm 2.13.1 Helm 引入 2.13.2 使用 Helm 可以解决哪些问题 2.13.3 Helm 概述 2.13.4 Helm 的 3 个重要概念 ...

  5. 【AngularJs学习笔记三】Grunt任务管理器

    为什么80%的码农都做不了架构师?>>>    #0 系列目录# AngularJs学习笔记 [AngularJs学习笔记一]Bower解决js的依赖管理 [AngularJs学习笔 ...

  6. ROS学习笔记三:创建ROS软件包

    ,# ROS学习笔记三:创建ROS软件包 catkin软件包的组成 一个软件包必须满足如下条件才能被称之为catkin软件包: 这个软件包必须包含一个catkin编译文件package.xml(man ...

  7. SurfaceFlinger学习笔记(三)之SurfaceFlinger进程

    概述 本系列是基于android Q 即android10 SurfaceFlinger学习笔记(一)应用启动流程 SurfaceFlinger学习笔记(二)之Surface SurfaceFling ...

  8. MySQL学习笔记(三)查询

    写在前面:本篇为作者自学总结,学习内容为课堂所学和网络学习笔记汇总,对于内容引用部分在文中和文末注明. 文章仅供参考,如需深入了解,请查阅MySQL参考手册.附上下载链接: 链接:https://pa ...

  9. 投资信托学习笔记(三)

    投资信托学习笔记(三) 投资信托学习笔记(三) - 补充笔记 百度百科信托投资 https://baike.baidu.com/item/%E4%BF%A1%E6%89%98%E6%8A%95%E8% ...

最新文章

  1. 技术人如何做职场沟通?
  2. 使用Harry过程中FAQ(问题解答)
  3. 人工智能思维导图一张,收藏!
  4. 面试:a==1 a==2 a==3 是 true 还是 false?
  5. 用c语言编程实现strcpy,用c语言.模拟实现strcpy,strcat,strcat,memcpy,memmove
  6. MATLAB怎么查找矩阵中所有0的数据并赋值
  7. Mahout推荐算法API详解
  8. 反思项目最新周数的反复反复修改
  9. 李嘉诚今日正式退休,来看看他一生都做过哪些牛逼的事
  10. 1.逐帧动画shader
  11. KAFKA 最新版 Shell API单机生产与消费
  12. list遍历_Qt 容器类之遍历器和隐式数据共享
  13. 使用源码安装 PostgreSQL 12.5 主从集群
  14. php做页面编辑器,最牛在线编辑器ueditor在thinkphp框架中的使用方法
  15. JForum3 学习笔记1
  16. 大型网站的系统架构(摘)
  17. LAMP环境的搭建与部署
  18. UIStepper (加减符号)(步进控件)
  19. 笔记本无线网卡驱动异常如何解决?
  20. 如何安装最新版本的office(preview预览版)、更新

热门文章

  1. 什么是CSM(Certified Scrum Master) 敏捷认证
  2. PDF 转成 一张图片
  3. 初中教师资格证计算机试题,2017上半年初中语文教师资格证面试试题(精选)第二批...
  4. 2018年首篇,带大家一起学习视图拖拽功能的源码
  5. oracle 逐层合计,Oracle数据仓库的分层管理器解决方案
  6. Lettuce(基于Python的BDD工具,中文编写自动化测试用例)
  7. python骂人代码大全_边看chromium的代码,边想骂人...
  8. Vue.js实战之系统学习第一节
  9. 关于绝地求生游戏白名单画中画逆向分析
  10. HDMI原理详解以及时序流程(视频是三对差分信号,音频Audio是PCM级(无压缩)传输,包含在数据包内,依靠协议规定采样)HDMI可以传输RGB与YUV两种格式