自定义request_Spring Security 自定义登录认证(二)
一、前言
本篇文章将讲述Spring Security自定义登录认证校验用户名、密码,自定义密码加密方式,以及在前后端分离的情况下认证失败或成功处理返回json格式数据
温馨小提示:Spring Security中有默认的密码加密方式以及登录用户认证校验,但小编这里选择自定义是为了方便以后业务扩展,比如系统默认带一个超级管理员,当认证时识别到是超级管理员账号登录访问时给它赋予最高权限,可以访问系统所有api接口,或在登录认证成功后存入token以便用户访问系统其它接口时通过token认证用户权限等
Spring Security入门学习可参考之前文章:
SpringBoot集成Spring Security入门体验(一)
二、Spring Security 自定义登录认证处理
基本环境
- spring-boot 2.1.8
- mybatis-plus 2.2.0
- mysql
- 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、自定义认证成功或失败处理方式
- 认证成功处理类实现
AuthenticationSuccessHandler
类重写onAuthenticationSuccess
方法 - 认证失败处理类实现
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. 输入正确用户名和账号,提示登陆成功,然后跳转到首页
登陆成功后即可正常访问其他接口,如果是未登录情况下将访问不了
温馨小提示:这里在未登录时或访问未授权的接口时,后端暂时没有做处理,相关案例将会放在后面的权限控制案例教程中讲解
六、总结
- 在
Spring Security核心配置类
中设置自定义的用户密码校验过滤器(AdminAuthenticationProcessingFilter)
- 在自定义的用户密码校验过滤器中配置
认证管理器(CusAuthenticationManager)
、认证成功处理(AdminAuthenticationSuccessHandler)
和认证失败处理(AdminAuthenticationFailureHandler)
等 - 在自定义的认证管理器中配置自定义的
认证处理(AdminAuthenticationProvider)
- 然后就是在认证处理中实现自己的相应业务逻辑等
Security相关代码结构:
本文案例源码
https://gitee.com/zhengqingya/java-workspace
自定义request_Spring Security 自定义登录认证(二)相关推荐
- spring security+jwt 登录认证
spring security+jwt 登录认证 1.综述 2.版本与环境 3.架构 4.数据库认证逻辑图 5.案例 security+jwt 5.1引入依赖 5.2新建工具类 5.2新建组件类 5. ...
- 清晰搞懂Spring Security的登录认证
文章目录 前言 1.简介 2.登录认证 2.1.过滤器链 2.2.认证流程 2.3.大思路分析 3.撸袖开干 3.1.环境搭建 3.1.1.数据库 3.1.2.项目搭建 3.1.3.工具类.部分配置类 ...
- Spring Security进行登录认证和授权
一.Spring Security内部认证流程 用户首次登录提交用户名和密码后spring security 的UsernamePasswordAuthenticationFilter把用户名密码封装 ...
- SpringBoot+Security+Jwt登录认证与权限控制(一)
一.相关技术 1. Maven 项目管理工具 2. MybatisPlus 3. SpringBoot 2.7.0 4. Security 安全框架 5. Jwt 6. easy-captcha 验证 ...
- H5页面使用微信网页授权实现登录认证
在用H5开发微信公众号页面应用时,往往需要获取微信的用户信息,H5页面在微信属于访问第三方网页,因此通过微信网页授权机制,来获取用户基本信息,此处需要用户确认授权才能获取,用户确认授权后,我们可以认为 ...
- (二)Spring Security自定义登录成功或失败处理器
目录 一:创建登录成功处理器 二:创建登录失败处理器 三:添加处理器 三. 项目地址 我们接着上一章 Spring Security最简单的搭建,进行开发 LoginSuccessHandler 和L ...
- spring security 自定义认证登录
spring security 自定义认证登录 1.概要 1.1.简介 spring security是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,以此来管理权限认证等. 1 ...
- SpringBoot 整合Security——自定义表单登录
文章目录 一.添加验证码 1.1 验证servlet 1.2 修改 login.html 1.3 添加匿名访问 Url 二.AJAX 验证 三.过滤器验证 3.1 编写验证码过滤器 3.2 注入过滤器 ...
- 项目1在线交流平台-7.构建安全高效的企业服务-2.使用Security自定义社区网页认证与授权
文章目录 功能需求 一. 废弃登录检查的拦截器 二.授权配置 1. 导包 2. Security配置 2.1 `WebSecurity` 2.2`HttpSecurity` ` http.author ...
最新文章
- CodeForces Round #287 Div.2
- 对javascript闭包的理解
- mysql cmake错误_MySQL5.5安装出现CMake错误找不到CMakelists.txt原因-阿里云开发者社区...
- Apache Tomcat目录下各个文件夹的作用
- 工作137:map函数
- PMP考试必看的答题技巧分享
- 42张PPT揭秘字节跳动人力资源体系(推荐收藏)
- 致远互联携手华为云启动开发者大赛,加速企业应用定制向平台生态转型
- iOS 开发 初级:应用内购买 In-App Purchase
- Deeplearning4j 快速入门
- 保利威视视频云平台 新版本(Version 1.1.0) 上线通知
- 半年营收200亿,翻台率却降到3次/天,老板娘出逃海底捞怎么了?
- 基于Java Web的在线考试系统的实现
- 位运算常用技巧分析汇总(算法进阶)
- [读书笔记]《自控力》
- 公比为无理数的等比数列的近似表示
- Excel VBA自动化办公:选择Excel文件合并订单数据生成订单汇总表、生成发货单并导出pdf文件、自动统计业绩生成业绩表
- Transfer Learning从入门到放弃(二)
- matlab经典实例,BP神经网络matlab实例(简单而经典)
- Js——ScrollTop、ScrollHeight、ClientHeight、OffsetHeight汇总
热门文章
- 终于成功地在Pluto中部署了一个Portlet了
- 什么是BGP,BGP的优点有哪些?-Vecloud
- DHCP服务器是什么?-Vecloud
- 【数据库】将Excel导入达梦数据库,并执行表合并
- Linux文件权限(3)
- 3月13日 抽奖活动
- JavaScript的编码规范
- springmvc+log4j操作日志记录,详细配置
- win7 右键菜单增加“在此以管理模式运行命令行”
- PowerDesigner12 逆向工程DataBase SQl2005: unable to list the tables 信息