SpringSecurity實現(xiàn)自定義登錄接口的詳細過程
SpringSecurity實現(xiàn)自定義登錄接口
1、配置類 ConfigClazz(SpringSecuriey的)
//首先就是要有一個配置類 @Resource private DIYUsernamePasswordAuthenticationFilter diyUsernamePasswordAuthenticationFilter; /*SpringSecurity配置*/ @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests( authorize -> authorize .requestMatchers("/user/**","/").hasRole("user") //擁有user的角色可訪問的接口 .requestMatchers("/manager/**").hasRole("manager")//擁有manager的角色可訪問的接口 .requestMatchers("/login/**").permitAll() .anyRequest() .authenticated() // 任何請求都需要授權(quán),重定向到 ); /*登錄頁*/ http.formLogin(AbstractHttpConfigurer::disable);//禁用默認的登錄接口,使用自定義的登錄接口 /*登出*/ http.logout(logout ->{ logout .logoutUrl("/goOut").permitAll() //登錄退出成功,向前端返回json格式的字符串 .logoutSuccessHandler((HttpServletRequest request, HttpServletResponse response, Authentication authentication)->{ Map<String, String[]> parameterMap = request.getParameterMap(); //進入登錄頁時,判斷是否已經(jīng)登陸過 TowLogin 參數(shù) if(!parameterMap.isEmpty() && parameterMap.get("TowLogin")[0].equals("true")){ String json = JSON.toJSONString(Code.NOTowLogin); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(json); } else { String json = JSON.toJSONString(Code.SuccessLogout); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(json); } }); }); /*向過濾器鏈中添加自定義的過濾器 用自定義的過濾器代替 UsernamePasswordAuthenticationFilter 過濾器 */ http.addFilterAfter(diyUsernamePasswordAuthenticationFilter, LogoutFilter.class); /*請求異常處理*/ http.exceptionHandling(exception ->{ /*用戶未登錄時,訪問限權(quán)接口,返回 json 格式的字符串 這個配是。把頁面跳轉(zhuǎn)交給前端,即:用戶未登錄時,后端只返回 json 格式的字符串,不會跳轉(zhuǎn)頁面 -- 未登錄時,重定向的 url .loginPage("/login/getLoginHTML").permitAll(),就不起作用了 -- */ exception.authenticationEntryPoint((HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)->{ String json = JSON.toJSONString(Code.NoLogin); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(json); }); //響應(yīng)登錄用戶訪問未授權(quán)路徑時(user角色訪問manager角色的接口) 有 未授權(quán) json 提示 exception.accessDeniedHandler((HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)->{ String json = JSON.toJSONString(Code.Forbidden); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(json); }); }); /*會話管理*/ http.sessionManagement(session -> { session //表示,最大連接數(shù)量為 1 ,同一個賬號,最多只能在一臺設(shè)備上登錄,當?shù)诙€登陸時,會把第一個擠掉 .maximumSessions(1) //擠掉后,對前端返回的json字符串 .expiredSessionStrategy((SessionInformationExpiredEvent event)->{ String json = JSON.toJSONString(Code.ForeignLogin); HttpServletResponse response = event.getResponse(); response.setContentType("application/json;charset=UTF-8"); response.getWriter().println(json); }); }); /*開啟跨域訪問*/ http.cors(withDefaults()); /* 禁用csrf的防御手段。 * 開啟后,相當于每次前端訪問接口的時候 * 都需要攜帶_crsf為參數(shù)名的參數(shù),功能類似于 token, * 因此建議禁用 * */ http.csrf(AbstractHttpConfigurer::disable); return http.build(); } //設(shè)置密碼的編碼方式(必須有) @Bean public BCryptPasswordEncoder bCryptPasswordEncoder(){ return new BCryptPasswordEncoder(10); }
解釋 _scrf 在哪看,只有最初有,后面就沒有,但是如果不攜帶,就不讓你訪問接口,因此建議禁用
2、DIYUsernamePasswordAuthenticationFilter
該類用于替換 UsernamePasswordAuthenticationFilter 過濾器,應(yīng)用自己自定義的過濾器
@Component //相當于 UsernamePasswordAuthenticationFilter public class DIYUsernamePasswordAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { /*問題:不能讀取請求體中的信息,因為是一次性的,讀完,后面就不能用了 * 因此,這里避免用json格式傳輸 賬號 和 密碼 * */ //獲取非 json 格式傳輸?shù)?,OK了,只要前端給 json 格式 的token就能獲取了 Map<String, String[]> parameterMap = request.getParameterMap(); //有前端打開 SUser user = null; HttpSession session = request.getSession(); //有前端打開 //檢查token,通過token解析出用戶的賬號,根據(jù)賬號,從 session 中查詢 if(parameterMap.get("token") != null) user = (SUser)session.getAttribute(parameterMap.get("token")[0]); if (user == null) { //放行,表示已經(jīng)退出,需要重新驗證,區(qū)別就是有沒有 存入SecurityContextHolder 一步驟 filterChain.doFilter(request, response); return; } //存入SecurityContextHolder,獲取權(quán)限信息封裝到Authentication中 UsernamePasswordAuthenticationToken authenticationToken = // 沒有前端獲取用戶數(shù)據(jù)目前先這樣寫 new UsernamePasswordAuthenticationToken(user,user.getPassword(),user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); //驗證成功,放行 filterChain.doFilter(request, response); } }
3、DIYAuthenticationProvider
該類是發(fā)放授權(quán)的接口
@Component public class DIYAuthenticationProvider implements AuthenticationProvider { @Resource private UserDetailsService userDetailsService; @Resource private PasswordEncoder passwordEncoder; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = (String) authentication.getCredentials(); // 從數(shù)據(jù)庫中加載用戶信息 UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 檢查密碼是否正確 if (!passwordEncoder.matches(password, userDetails.getPassword())) { throw new BadCredentialsException("用戶名或密碼錯誤"); } // 創(chuàng)建一個已認證的 Authentication 對象 UsernamePasswordAuthenticationToken authenticatedToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities()); authenticatedToken.setDetails(authentication.getDetails()); return authenticatedToken; } @Override public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } }
4、DIYAuthenticationManager
該類是用來調(diào)用發(fā)放授權(quán)接口
@Component public class DIYAuthenticationManager implements AuthenticationManager { @Resource //這里雖然是注入的接口,但是由于自定義的類 DIYAuthenticationProvider 實現(xiàn)了該接口,因此優(yōu)先使用 AuthenticationProvider authenticationProvider; //這里其實可以調(diào)用默認的 授權(quán)提供者,有匹配的就會授權(quán),但是,沒必要,因為肯定匹配不了,最后還是用自己的, @Override //那不如 直接就用自己的就好了 public Authentication authenticate(Authentication authentication) throws AuthenticationException { return authenticationProvider.authenticate(authentication); } }
5、MySQLUserDetailsManager
該類用于獲取用戶的信息
@Component //將這個類交給Spring容器管理,即:創(chuàng)建該類的 bean 對象,進而取代(重寫)原來的方法 public class MySQLUserDetailsManager implements UserDetailsService{ //由于是基于數(shù)據(jù)庫的,因此,只需要實現(xiàn)一個 UserDetailsService 接口就好,不需要實現(xiàn)其他的接口 @Resource //這個是Mapper接口,用于從數(shù)據(jù)庫中調(diào)用查詢信息 SUserMapper sUserMapper; @Resource //這個是必要的 HttpServletRequest request; @Override //String username public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException { //獲取數(shù)據(jù)信息要在這里開始,由于只暴露用戶輸入account,因此數(shù)據(jù)庫中的數(shù)據(jù)只能,所有的 account都不一樣,才能唯一匹配 account,這里 Email 一定不一樣 //這里的 username 就是用戶輸入的賬號,為了方便,就換一個變量名 account List<SUser> sUsers = sUserMapper.selectAllByEmail(account); //這里 Email 一定不一樣 if(sUsers != null && !sUsers.isEmpty()) { SUser sUser = sUsers.get(0); //這里把 authenticate 這個用戶的信息存到session中,如果調(diào)用退出登錄接口,就會刪除session里面的內(nèi)容 HttpSession session = request.getSession(); session.setAttribute(String.valueOf(sUser.getEmail()),sUser); return sUser; } else { throw new UsernameNotFoundException(account); } } }
6、控制層
@Controller @Tag(name = "登錄注冊") @RequestMapping("/login") public class LoginController { @Resource private SUserService sUserService; @Resource private AuthenticationManager authenticationManager; @GetMapping("/getLoginHTML") //進入登錄頁的接口 public String getLoginHtml(HttpSession session){ boolean aNew = session.isNew(); if(aNew) return "login"; //如果一個瀏覽器試圖登錄兩次,那么就會直接調(diào)用退出接口 return "redirect:/goOut?TowLogin=true"; } @PostMapping("/ooo") //由于是自定義登錄接口,因此什么請求都可以,建議用Post @ResponseBody //將返回值寫入響應(yīng)體中 public Code login(String account,String password){ SUser sUser = new SUser(); sUser.setEmail(account); sUser.setPassword(password); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(sUser,password); Authentication authenticate = authenticationManager.authenticate(authenticationToken); if(Objects.isNull(authenticate)) throw new AuthenticationCredentialsNotFoundException("用戶賬號或密碼錯誤"); else{ //這里響應(yīng)回去一個 token,根據(jù)賬號加密后,生成的 token Map<String, String> map = new HashMap<>(); map.put("token",authenticate.getName()); return new Code<>(Code.OK, map); } }
7、增強用戶的實體類
這里由于要封裝用戶的詳細信息,而用 MybatisX 生成的 User 實體類不能滿足需求,因此要實現(xiàn)一個接口
@TableName(value ="s_user") @Data @Repository //將這個類交給IOC容器(Spring)管理 public class SUser implements Serializable , UserDetails{ //實現(xiàn)這個接口 /** * 主鍵id,自動遞增 */ @TableId(type = IdType.AUTO) private Integer id; /** * 用戶名:<=10 */ private String name; /** * 年齡 */ private Integer age; /** * 性別:女 , 男 */ private String sex; /** * 郵箱賬號:<=30 */ private String email; /** * 密碼:<=15 */ private String password; /** * 是否被禁用:0-未禁用,1-已禁用 */ private Integer isForbidden; /** * 該賬號的角色:0-普通用戶,1-管理員 */ private String role; /** * 是否被刪除(或用戶注銷):0-未刪除,1-刪除 */ @TableLogic private Integer isDelete; @Serial @TableField(exist = false) private static final long serialVersionUID = 1L; @Override public Collection<? extends GrantedAuthority> getAuthorities() { /*這里要自己拼接 ROLE_ + role * ROLE_ : 是固定的 * 由于我這里的實體類設(shè)計的是:String role; 不是數(shù)組形式,因此不用循環(huán) * 如果是數(shù)組形式的限權(quán),循環(huán)遍歷,并創(chuàng)建 SimpleGrantedAuthority 就好了 * */ List<SimpleGrantedAuthority> list = new ArrayList<>(); list.add(new SimpleGrantedAuthority("ROLE_" + role)); return list; } @Override //注意:這里的用戶名是 賬號 public String getUsername() { return this.email; } @Override//沒有這個設(shè)定就返回通過的結(jié)果,可以用翻譯 isAccountNonExpired ? 在每個方法名后加一個? 問自己是true/false public boolean isAccountNonExpired() { return true; } @Override//自己的實體類中有這個設(shè)定,就返回判斷的結(jié)果 public boolean isAccountNonLocked() { return isForbidden == 1; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } }
8、依賴
java版本 17
springBoot版本 3.2.0
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--SpringSecurity依賴--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--thymeleaf作為視圖模板--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--mybatis-Puls的依賴--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.4.1</version> <!--由于SpringBoot的版本太高,需要這樣1--> <exclusions> <exclusion> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </exclusion> </exclusions> </dependency> <!--由于SpringBoot的版本太高,需要這樣2--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>3.0.3</version> </dependency> <!--mysql的驅(qū)動包--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> <!--簡化實體類開發(fā)--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--JavaWeb組件--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--引入json數(shù)據(jù)依賴,用于給前端返回json類型的數(shù)據(jù)--> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.37</version> </dependency> <!--knife4j測試,對請求的測試,有兩種,swagger-ui.html / doc.html 都可以--> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <version>4.4.0</version> </dependency> </dependencies>
到此這篇關(guān)于SpringSecurity實現(xiàn)自定義登錄接口的文章就介紹到這了,更多相關(guān)SpringSecurity自定義登錄接口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java針對于時間轉(zhuǎn)換的DateUtils工具類
這篇文章主要為大家詳細介紹了java針對于時間轉(zhuǎn)換的DateUtils工具類,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12Mybatis如何根據(jù)List批量查詢List結(jié)果
這篇文章主要介紹了Mybatis如何根據(jù)List批量查詢List結(jié)果,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03詳解Guava Cache本地緩存在Spring Boot應(yīng)用中的實踐
Guava Cache是一個全內(nèi)存的本地緩存實現(xiàn),本文將講述如何將 Guava Cache緩存應(yīng)用到 Spring Boot應(yīng)用中。具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-01-01深度優(yōu)先與廣度優(yōu)先Java實現(xiàn)代碼示例
這篇文章主要介紹了深度優(yōu)先與廣度優(yōu)先Java實現(xiàn)代碼示例,具有一定借鑒價值,需要的朋友可以參考下。2017-12-12Mybatis Plus 3.4.0分頁攔截器的用法小結(jié)
本文主要介紹了Mybatis Plus 3.4.0分頁攔截器的用法小結(jié),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2025-03-03Spring?boot?整合RabbitMQ實現(xiàn)通過RabbitMQ進行項目的連接
RabbitMQ是一個開源的AMQP實現(xiàn),服務(wù)器端用Erlang語言編寫,支持多種客戶端,這篇文章主要介紹了Spring?boot?整合RabbitMQ實現(xiàn)通過RabbitMQ進行項目的連接,需要的朋友可以參考下2022-10-10