javascript
自定义request_Spring Security 自定义登录认证(二)
一、前言
本篇文章將講述Spring Security自定義登錄認(rèn)證校驗(yàn)用戶(hù)名、密碼,自定義密碼加密方式,以及在前后端分離的情況下認(rèn)證失敗或成功處理返回json格式數(shù)據(jù)
溫馨小提示:Spring Security中有默認(rèn)的密碼加密方式以及登錄用戶(hù)認(rèn)證校驗(yàn),但小編這里選擇自定義是為了方便以后業(yè)務(wù)擴(kuò)展,比如系統(tǒng)默認(rèn)帶一個(gè)超級(jí)管理員,當(dāng)認(rèn)證時(shí)識(shí)別到是超級(jí)管理員賬號(hào)登錄訪(fǎng)問(wèn)時(shí)給它賦予最高權(quán)限,可以訪(fǎng)問(wèn)系統(tǒng)所有api接口,或在登錄認(rèn)證成功后存入token以便用戶(hù)訪(fǎng)問(wèn)系統(tǒng)其它接口時(shí)通過(guò)token認(rèn)證用戶(hù)權(quán)限等
Spring Security入門(mén)學(xué)習(xí)可參考之前文章:
SpringBoot集成Spring Security入門(mén)體驗(yàn)(一)
二、Spring Security 自定義登錄認(rèn)證處理
基本環(huán)境
數(shù)據(jù)庫(kù)用戶(hù)信息表t_sys_user
案例中關(guān)于對(duì)該t_sys_user用戶(hù)表相關(guān)的增刪改查代碼就不貼出來(lái)了,如有需要可參考文末提供的案例demo源碼1、Security 核心配置類(lèi)
配置用戶(hù)密碼校驗(yàn)過(guò)濾器
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 用戶(hù)密碼校驗(yàn)過(guò)濾器*/private final AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter;public SecurityConfig(AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter) {this.adminAuthenticationProcessingFilter = adminAuthenticationProcessingFilter;}/*** 權(quán)限配置* @param http* @throws Exception*/@Overrideprotected void configure(HttpSecurity http) throws Exception {ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.antMatcher("/**").authorizeRequests();// 禁用CSRF 開(kāi)啟跨域http.csrf().disable().cors();// 登錄處理 - 前后端一體的情況下 // registry.and().formLogin().loginPage("/login").defaultSuccessUrl("/").permitAll() // // 自定義登陸用戶(hù)名和密碼屬性名,默認(rèn)為 username和password // .usernameParameter("username").passwordParameter("password") // // 異常處理 // .failureUrl("/login/error").permitAll() // // 退出登錄 // .and().logout().permitAll();// 標(biāo)識(shí)只能在 服務(wù)器本地ip[127.0.0.1或localhost] 訪(fǎng)問(wèn)`/home`接口,其他ip地址無(wú)法訪(fǎng)問(wèn)registry.antMatchers("/home").hasIpAddress("127.0.0.1");// 允許匿名的url - 可理解為放行接口 - 多個(gè)接口使用,分割registry.antMatchers("/login", "/index").permitAll();// OPTIONS(選項(xiàng)):查找適用于一個(gè)特定網(wǎng)址資源的通訊選擇。 在不需執(zhí)行具體的涉及數(shù)據(jù)傳輸?shù)膭?dòng)作情況下, 允許客戶(hù)端來(lái)確定與資源相關(guān)的選項(xiàng)以及 / 或者要求, 或是一個(gè)服務(wù)器的性能registry.antMatchers(HttpMethod.OPTIONS, "/**").denyAll();// 自動(dòng)登錄 - cookie儲(chǔ)存方式registry.and().rememberMe();// 其余所有請(qǐng)求都需要認(rèn)證registry.anyRequest().authenticated();// 防止iframe 造成跨域registry.and().headers().frameOptions().disable();// 自定義過(guò)濾器認(rèn)證用戶(hù)名密碼http.addFilterAt(adminAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class);} }2、自定義用戶(hù)密碼校驗(yàn)過(guò)濾器
@Slf4j @Component public class AdminAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {/*** @param authenticationManager: 認(rèn)證管理器* @param adminAuthenticationSuccessHandler: 認(rèn)證成功處理* @param adminAuthenticationFailureHandler: 認(rèn)證失敗處理*/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("請(qǐng)求頭類(lèi)型不支持: " + request.getContentType());}UsernamePasswordAuthenticationToken authRequest;try {MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);// 將前端傳遞的數(shù)據(jù)轉(zhuǎn)換成jsonBean數(shù)據(jù)格式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、自定義認(rèn)證管理器
@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、自定義認(rèn)證處理
這里的密碼加密驗(yàn)證工具類(lèi)PasswordUtils可在文末源碼中查看
@Component public class AdminAuthenticationProvider implements AuthenticationProvider {@AutowiredUserDetailsServiceImpl userDetailsService;@Autowiredprivate UserMapper userMapper;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 獲取前端表單中輸入后返回的用戶(hù)名、密碼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());// 驗(yàn)證密碼if (!isValid) {throw new BadCredentialsException("密碼錯(cuò)誤!");}// 前后端分離情況下 處理邏輯...// 更新登錄令牌 - 之后訪(fǎng)問(wèn)系統(tǒng)其它接口直接通過(guò)token認(rèn)證用戶(hù)權(quán)限...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;} }其中小編自定義了一個(gè)UserDetailsServiceImpl類(lèi)去實(shí)現(xiàn)UserDetailsService類(lèi) -> 用于認(rèn)證用戶(hù)詳情 和自定義一個(gè)SecurityUser類(lèi)實(shí)現(xiàn)UserDetails類(lèi) -> 安全認(rèn)證用戶(hù)詳情信息
@Service("userDetailsService") public class UserDetailsServiceImpl implements UserDetailsService {@Autowiredprivate UserMapper userMapper;/**** 根據(jù)賬號(hào)獲取用戶(hù)信息* @param username:* @return: org.springframework.security.core.userdetails.UserDetails*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 從數(shù)據(jù)庫(kù)中取出用戶(hù)信息List<User> userList = userMapper.selectList(new EntityWrapper<User>().eq("username", username));User user;// 判斷用戶(hù)是否存在if (!CollectionUtils.isEmpty(userList)){user = userList.get(0);} else {throw new UsernameNotFoundException("用戶(hù)名不存在!");}// 返回UserDetails實(shí)現(xiàn)類(lèi)return new SecurityUser(user);} }安全認(rèn)證用戶(hù)詳情信息
@Data @Slf4j public class SecurityUser implements UserDetails {/*** 當(dāng)前登錄用戶(hù)*/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、自定義認(rèn)證成功或失敗處理方式
認(rèn)證成功后這里小編只返回了一個(gè)token給前端,其它信息可根據(jù)個(gè)人業(yè)務(wù)實(shí)際處理
@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));} }認(rèn)證失敗捕捉異常自定義錯(cuò)誤信息返回給前端
@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("賬戶(hù)被鎖定,請(qǐng)聯(lián)系管理員!");} else if (e instanceof CredentialsExpiredException) {result = ApiResult.fail("證書(shū)過(guò)期,請(qǐng)聯(lián)系管理員!");} else if (e instanceof AccountExpiredException) {result = ApiResult.fail("賬戶(hù)過(guò)期,請(qǐng)聯(lián)系管理員!");} else if (e instanceof DisabledException) {result = ApiResult.fail("賬戶(hù)被禁用,請(qǐng)聯(lián)系管理員!");} else {log.error("登錄失敗:", e);result = ApiResult.fail("登錄失敗!");}ResponseUtils.out(response, result);} }溫馨小提示:
前后端一體的情況下可通過(guò)在Spring Security核心配置類(lèi)中配置異常處理接口然后通過(guò)如下方式獲取異常信息
AuthenticationException e = (AuthenticationException) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION"); System.out.println(e.getMessage());三、前端頁(yè)面
這里2個(gè)簡(jiǎn)單的html頁(yè)面模擬前后端分離情況下登陸處理場(chǎng)景
1、登陸頁(yè)
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>用戶(hù)名:<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"/>自動(dòng)登錄</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、首頁(yè)
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>四、測(cè)試接口
@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")// 訪(fǎng)問(wèn)路徑`/admin` 具有`crud`權(quán)限@PreAuthorize("hasPermission('/admin','crud')")public String admin() {return "Hello~ 管理員";}@GetMapping("/test") // @PreAuthorize("hasPermission('/test','t')")public String test() {return "Hello~ 測(cè)試權(quán)限訪(fǎng)問(wèn)接口";}/*** 登錄異常處理 - 前后端一體的情況下* @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()));} }五、測(cè)試訪(fǎng)問(wèn)效果
數(shù)據(jù)庫(kù)賬號(hào):admin 密碼:123456
1. 輸入錯(cuò)誤用戶(hù)名提示該用戶(hù)不存在
2. 輸入錯(cuò)誤密碼提示密碼錯(cuò)誤
3. 輸入正確用戶(hù)名和賬號(hào),提示登陸成功,然后跳轉(zhuǎn)到首頁(yè)
登陸成功后即可正常訪(fǎng)問(wèn)其他接口,如果是未登錄情況下將訪(fǎng)問(wèn)不了
溫馨小提示:這里在未登錄時(shí)或訪(fǎng)問(wèn)未授權(quán)的接口時(shí),后端暫時(shí)沒(méi)有做處理,相關(guān)案例將會(huì)放在后面的權(quán)限控制案例教程中講解六、總結(jié)
Security相關(guān)代碼結(jié)構(gòu):
本文案例源碼
https://gitee.com/zhengqingya/java-workspace
總結(jié)
以上是生活随笔為你收集整理的自定义request_Spring Security 自定义登录认证(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: vtuber面部捕捉工具_泰国程序员开发
- 下一篇: 四面体的表面积_JACS:武汉大学汪成课