一、前言

本篇文章将讲述Spring Security自定义登录认证校验用户名、密码,自定义密码加密方式,以及在前后端分离的情况下认证失败或成功处理返回json格式数据

温馨小提示:Spring Security中有默认的密码加密方式以及登录用户认证校验,但小编这里选择自定义是为了方便以后业务扩展,比如系统默认带一个超级管理员,当认证时识别到是超级管理员账号登录访问时给它赋予最高权限,可以访问系统所有api接口,或在登录认证成功后存入token以便用户访问系统其它接口时通过token认证用户权限等

Spring Security入门学习可参考之前文章:

SpringBoot集成Spring Security入门体验(一)

二、Spring Security 自定义登录认证处理

基本环境

  1. spring-boot 2.1.8
  2. mybatis-plus 2.2.0
  3. mysql
  4. maven项目

数据库用户信息表t_sys_user

案例中关于对该t_sys_user用户表相关的增删改查代码就不贴出来了,如有需要可参考文末提供的案例demo源码

1、Security 核心配置类

配置用户密码校验过滤器

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 用户密码校验过滤器*/private final AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter;public SecurityConfig(AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter) {this.adminAuthenticationProcessingFilter = adminAuthenticationProcessingFilter;}/*** 权限配置* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.antMatcher("/**").authorizeRequests();// 禁用CSRF 开启跨域http.csrf().disable().cors();// 登录处理 - 前后端一体的情况下
//        registry.and().formLogin().loginPage("/login").defaultSuccessUrl("/").permitAll()
//                // 自定义登陆用户名和密码属性名,默认为 username和password
//                .usernameParameter("username").passwordParameter("password")
//                // 异常处理
//                .failureUrl("/login/error").permitAll()
//                // 退出登录
//                .and().logout().permitAll();// 标识只能在 服务器本地ip[127.0.0.1或localhost] 访问`/home`接口,其他ip地址无法访问registry.antMatchers("/home").hasIpAddress("127.0.0.1");// 允许匿名的url - 可理解为放行接口 - 多个接口使用,分割registry.antMatchers("/login", "/index").permitAll();// OPTIONS(选项):查找适用于一个特定网址资源的通讯选择。 在不需执行具体的涉及数据传输的动作情况下, 允许客户端来确定与资源相关的选项以及 / 或者要求, 或是一个服务器的性能registry.antMatchers(HttpMethod.OPTIONS, "/**").denyAll();// 自动登录 - cookie储存方式registry.and().rememberMe();// 其余所有请求都需要认证registry.anyRequest().authenticated();// 防止iframe 造成跨域registry.and().headers().frameOptions().disable();// 自定义过滤器认证用户名密码http.addFilterAt(adminAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class);}
}

2、自定义用户密码校验过滤器

@Slf4j
@Component
public class AdminAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {/*** @param authenticationManager:             认证管理器* @param adminAuthenticationSuccessHandler: 认证成功处理* @param adminAuthenticationFailureHandler: 认证失败处理*/public AdminAuthenticationProcessingFilter(CusAuthenticationManager authenticationManager, AdminAuthenticationSuccessHandler adminAuthenticationSuccessHandler, AdminAuthenticationFailureHandler adminAuthenticationFailureHandler) {super(new AntPathRequestMatcher("/login", "POST"));this.setAuthenticationManager(authenticationManager);this.setAuthenticationSuccessHandler(adminAuthenticationSuccessHandler);this.setAuthenticationFailureHandler(adminAuthenticationFailureHandler);}@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (request.getContentType() == null || !request.getContentType().contains(Constants.REQUEST_HEADERS_CONTENT_TYPE)) {throw new AuthenticationServiceException("请求头类型不支持: " + request.getContentType());}UsernamePasswordAuthenticationToken authRequest;try {MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);// 将前端传递的数据转换成jsonBean数据格式User user = JSONObject.parseObject(wrappedRequest.getBodyJsonStrByJson(wrappedRequest), User.class);authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), null);authRequest.setDetails(authenticationDetailsSource.buildDetails(wrappedRequest));} catch (Exception e) {throw new AuthenticationServiceException(e.getMessage());}return this.getAuthenticationManager().authenticate(authRequest);}
}

3、自定义认证管理器

@Component
public class CusAuthenticationManager implements AuthenticationManager {private final AdminAuthenticationProvider adminAuthenticationProvider;public CusAuthenticationManager(AdminAuthenticationProvider adminAuthenticationProvider) {this.adminAuthenticationProvider = adminAuthenticationProvider;}@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {Authentication result = adminAuthenticationProvider.authenticate(authentication);if (Objects.nonNull(result)) {return result;}throw new ProviderNotFoundException("Authentication failed!");}
}

4、自定义认证处理

这里的密码加密验证工具类PasswordUtils可在文末源码中查看

@Component
public class AdminAuthenticationProvider implements AuthenticationProvider {@AutowiredUserDetailsServiceImpl userDetailsService;@Autowiredprivate UserMapper userMapper;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 获取前端表单中输入后返回的用户名、密码String userName = (String) authentication.getPrincipal();String password = (String) authentication.getCredentials();SecurityUser userInfo = (SecurityUser) userDetailsService.loadUserByUsername(userName);boolean isValid = PasswordUtils.isValidPassword(password, userInfo.getPassword(), userInfo.getCurrentUserInfo().getSalt());// 验证密码if (!isValid) {throw new BadCredentialsException("密码错误!");}// 前后端分离情况下 处理逻辑...// 更新登录令牌 - 之后访问系统其它接口直接通过token认证用户权限...String token = PasswordUtils.encodePassword(System.currentTimeMillis() + userInfo.getCurrentUserInfo().getSalt(), userInfo.getCurrentUserInfo().getSalt());User user = userMapper.selectById(userInfo.getCurrentUserInfo().getId());user.setToken(token);userMapper.updateById(user);userInfo.getCurrentUserInfo().setToken(token);return new UsernamePasswordAuthenticationToken(userInfo, password, userInfo.getAuthorities());}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}

其中小编自定义了一个UserDetailsServiceImpl类去实现UserDetailsService类 -> 用于认证用户详情 和自定义一个SecurityUser类实现UserDetails类 -> 安全认证用户详情信息

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;/**** 根据账号获取用户信息* @param username:* @return: org.springframework.security.core.userdetails.UserDetails*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 从数据库中取出用户信息List<User> userList = userMapper.selectList(new EntityWrapper<User>().eq("username", username));User user;// 判断用户是否存在if (!CollectionUtils.isEmpty(userList)){user = userList.get(0);} else {throw new UsernameNotFoundException("用户名不存在!");}// 返回UserDetails实现类return new SecurityUser(user);}
}

安全认证用户详情信息

@Data
@Slf4j
public class SecurityUser implements UserDetails {/*** 当前登录用户*/private transient User currentUserInfo;public SecurityUser() {}public SecurityUser(User user) {if (user != null) {this.currentUserInfo = user;}}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {Collection<GrantedAuthority> authorities = new ArrayList<>();SimpleGrantedAuthority authority = new SimpleGrantedAuthority("admin");authorities.add(authority);return authorities;}@Overridepublic String getPassword() {return currentUserInfo.getPassword();}@Overridepublic String getUsername() {return currentUserInfo.getUsername();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

5、自定义认证成功或失败处理方式

  1. 认证成功处理类实现AuthenticationSuccessHandler类重写onAuthenticationSuccess方法
  2. 认证失败处理类实现AuthenticationFailureHandler类重写onAuthenticationFailure方法

在前后端分离情况下小编认证成功和失败都返回json数据格式

认证成功后这里小编只返回了一个token给前端,其它信息可根据个人业务实际处理

@Component
public class AdminAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication auth) throws IOException, ServletException {User user = new User();SecurityUser securityUser = ((SecurityUser) auth.getPrincipal());user.setToken(securityUser.getCurrentUserInfo().getToken());ResponseUtils.out(response, ApiResult.ok("登录成功!", user));}
}

认证失败捕捉异常自定义错误信息返回给前端

@Slf4j
@Component
public class AdminAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {ApiResult result;if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {result = ApiResult.fail(e.getMessage());} else if (e instanceof LockedException) {result = ApiResult.fail("账户被锁定,请联系管理员!");} else if (e instanceof CredentialsExpiredException) {result = ApiResult.fail("证书过期,请联系管理员!");} else if (e instanceof AccountExpiredException) {result = ApiResult.fail("账户过期,请联系管理员!");} else if (e instanceof DisabledException) {result = ApiResult.fail("账户被禁用,请联系管理员!");} else {log.error("登录失败:", e);result = ApiResult.fail("登录失败!");}ResponseUtils.out(response, result);}
}

温馨小提示:

前后端一体的情况下可通过在Spring Security核心配置类中配置异常处理接口然后通过如下方式获取异常信息

AuthenticationException e = (AuthenticationException) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
System.out.println(e.getMessage());

三、前端页面

这里2个简单的html页面模拟前后端分离情况下登陆处理场景

1、登陆页

login.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Login</title>
</head>
<body>
<h1>Spring Security</h1>
<form method="post" action="" onsubmit="return false"><div>用户名:<input type="text" name="username" id="username"></div><div>密码:<input type="password" name="password" id="password"></div><div>
<!--        <label><input type="checkbox" name="remember-me" id="remember-me"/>自动登录</label>--><button onclick="login()">登陆</button></div>
</form>
</body>
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js" type="text/javascript"></script>
<script type="text/javascript">function login() {var username = document.getElementById("username").value;var password = document.getElementById("password").value;// var rememberMe = document.getElementById("remember-me").value;$.ajax({async: false,type: "POST",dataType: "json",url: '/login',contentType: "application/json",data: JSON.stringify({"username": username,"password": password// "remember-me": rememberMe}),success: function (result) {console.log(result)if (result.code == 200) {alert("登陆成功");window.location.href = "../home.html";} else {alert(result.message)}}});}
</script>
</html>

2、首页

home.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<h3>您好,登陆成功</h3>
<button onclick="window.location.href='/logout'">退出登录</button>
</body>
</html>

四、测试接口

@Slf4j
@RestController
public class IndexController {@GetMapping("/")public ModelAndView showHome() {return new ModelAndView("home.html");}@GetMapping("/index")public String index() {return "Hello World ~";}@GetMapping("/login")public ModelAndView login() {return new ModelAndView("login.html");}@GetMapping("/home")public String home() {String name = SecurityContextHolder.getContext().getAuthentication().getName();log.info("登陆人:" + name);return "Hello~ " + name;}@GetMapping(value ="/admin")// 访问路径`/admin` 具有`crud`权限@PreAuthorize("hasPermission('/admin','crud')")public String admin() {return "Hello~ 管理员";}@GetMapping("/test")
//    @PreAuthorize("hasPermission('/test','t')")public String test() {return "Hello~ 测试权限访问接口";}/*** 登录异常处理 - 前后端一体的情况下* @param request* @param response*/@RequestMapping("/login/error")public void loginError(HttpServletRequest request, HttpServletResponse response) {AuthenticationException e = (AuthenticationException) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");log.error(e.getMessage());ResponseUtils.out(response, ApiResult.fail(e.getMessage()));}
}

五、测试访问效果

数据库账号:admin 密码:123456

1. 输入错误用户名提示该用户不存在

2. 输入错误密码提示密码错误

3. 输入正确用户名和账号,提示登陆成功,然后跳转到首页

登陆成功后即可正常访问其他接口,如果是未登录情况下将访问不了

温馨小提示:这里在未登录时或访问未授权的接口时,后端暂时没有做处理,相关案例将会放在后面的权限控制案例教程中讲解

六、总结

  1. Spring Security核心配置类中设置自定义的用户密码校验过滤器(AdminAuthenticationProcessingFilter)
  2. 在自定义的用户密码校验过滤器中配置认证管理器(CusAuthenticationManager)认证成功处理(AdminAuthenticationSuccessHandler)认证失败处理(AdminAuthenticationFailureHandler)
  3. 在自定义的认证管理器中配置自定义的认证处理(AdminAuthenticationProvider)
  4. 然后就是在认证处理中实现自己的相应业务逻辑等

Security相关代码结构:

本文案例源码

https://gitee.com/zhengqingya/java-workspace

自定义request_Spring Security 自定义登录认证(二)相关推荐

  1. spring security+jwt 登录认证

    spring security+jwt 登录认证 1.综述 2.版本与环境 3.架构 4.数据库认证逻辑图 5.案例 security+jwt 5.1引入依赖 5.2新建工具类 5.2新建组件类 5. ...

  2. 清晰搞懂Spring Security的登录认证

    文章目录 前言 1.简介 2.登录认证 2.1.过滤器链 2.2.认证流程 2.3.大思路分析 3.撸袖开干 3.1.环境搭建 3.1.1.数据库 3.1.2.项目搭建 3.1.3.工具类.部分配置类 ...

  3. Spring Security进行登录认证和授权

    一.Spring Security内部认证流程 用户首次登录提交用户名和密码后spring security 的UsernamePasswordAuthenticationFilter把用户名密码封装 ...

  4. SpringBoot+Security+Jwt登录认证与权限控制(一)

    一.相关技术 1. Maven 项目管理工具 2. MybatisPlus 3. SpringBoot 2.7.0 4. Security 安全框架 5. Jwt 6. easy-captcha 验证 ...

  5. H5页面使用微信网页授权实现登录认证

    在用H5开发微信公众号页面应用时,往往需要获取微信的用户信息,H5页面在微信属于访问第三方网页,因此通过微信网页授权机制,来获取用户基本信息,此处需要用户确认授权才能获取,用户确认授权后,我们可以认为 ...

  6. (二)Spring Security自定义登录成功或失败处理器

    目录 一:创建登录成功处理器 二:创建登录失败处理器 三:添加处理器 三. 项目地址 我们接着上一章 Spring Security最简单的搭建,进行开发 LoginSuccessHandler 和L ...

  7. spring security 自定义认证登录

    spring security 自定义认证登录 1.概要 1.1.简介 spring security是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,以此来管理权限认证等. 1 ...

  8. SpringBoot 整合Security——自定义表单登录

    文章目录 一.添加验证码 1.1 验证servlet 1.2 修改 login.html 1.3 添加匿名访问 Url 二.AJAX 验证 三.过滤器验证 3.1 编写验证码过滤器 3.2 注入过滤器 ...

  9. 项目1在线交流平台-7.构建安全高效的企业服务-2.使用Security自定义社区网页认证与授权

    文章目录 功能需求 一. 废弃登录检查的拦截器 二.授权配置 1. 导包 2. Security配置 2.1 `WebSecurity` 2.2`HttpSecurity` ` http.author ...

最新文章

  1. CodeForces Round #287 Div.2
  2. 对javascript闭包的理解
  3. mysql cmake错误_MySQL5.5安装出现CMake错误找不到CMakelists.txt原因-阿里云开发者社区...
  4. Apache Tomcat目录下各个文件夹的作用
  5. 工作137:map函数
  6. PMP考试必看的答题技巧分享
  7. 42张PPT揭秘字节跳动人力资源体系(推荐收藏)
  8. 致远互联携手华为云启动开发者大赛,加速企业应用定制向平台生态转型
  9. iOS 开发 初级:应用内购买 In-App Purchase
  10. Deeplearning4j 快速入门
  11. 保利威视视频云平台 新版本(Version 1.1.0) 上线通知
  12. 半年营收200亿,翻台率却降到3次/天,老板娘出逃海底捞怎么了?
  13. 基于Java Web的在线考试系统的实现
  14. 位运算常用技巧分析汇总(算法进阶)
  15. [读书笔记]《自控力》
  16. 公比为无理数的等比数列的近似表示
  17. Excel VBA自动化办公:选择Excel文件合并订单数据生成订单汇总表、生成发货单并导出pdf文件、自动统计业绩生成业绩表
  18. Transfer Learning从入门到放弃(二)
  19. matlab经典实例,BP神经网络matlab实例(简单而经典)
  20. Js——ScrollTop、ScrollHeight、ClientHeight、OffsetHeight汇总

热门文章

  1. 终于成功地在Pluto中部署了一个Portlet了
  2. 什么是BGP,BGP的优点有哪些?-Vecloud
  3. DHCP服务器是什么?-Vecloud
  4. 【数据库】将Excel导入达梦数据库,并执行表合并
  5. Linux文件权限(3)
  6. 3月13日 抽奖活动
  7. JavaScript的编码规范
  8. springmvc+log4j操作日志记录,详细配置
  9. win7 右键菜单增加“在此以管理模式运行命令行”
  10. PowerDesigner12 逆向工程DataBase SQl2005: unable to list the tables 信息