SpringBoot?Security從入門到實戰(zhàn)示例教程
前言
Spring Security是一個功能強大且高度可定制的身份驗證和訪問控制框架。提供了完善的認證機制和方法級的授權(quán)功能。是一款非常優(yōu)秀的權(quán)限管理框架。它的核心是一組過濾器鏈,不同的功能經(jīng)由不同的過濾器。這篇文章給大家講解SpringBoot Security從入門到實戰(zhàn),內(nèi)容如下所示:
入門
測試接口
假設我們用下面的接口做權(quán)限測試。
@RestController public class LakerController { @GetMapping("/laker") public String laker() { return IdUtil.simpleUUID(); } @GetMapping("/laker/q") public String lakerQ() { return IdUtil.simpleUUID(); } }
瀏覽器訪問:http://localhost:8080/laker
,結(jié)果如下:
增加依賴
在 pom.xml,添加 spring-boot-starter-securtiy 依賴。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
再次訪問http://localhost:8080/laker
,結(jié)果如下:
簡要解析
我們訪問http://localhost:8080/laker
,security判斷我們沒有登錄,則會302重定向到http://localhost:8080/login
(默認)
- security會返回一個默認的登錄頁。
- 默認用戶名為:
user
,密碼在服務啟動時,會隨機生成一個,可以查看啟動日志如下:
2022-05-02 21:01:03.697 INFO 17896 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2022-05-02 21:01:03.825 INFO 17896 --- [ main] .s.s.UserDetailsServiceAutoConfiguration :Using generated security password: e53fef6a-3f61-43c3-9609-ce88fd7c0841
當然,可以通過配置文件設置默認的用戶名、密碼、角色。
spring: security: user: # 默認是 user name: laker password: laker roles: - ADMIN - TESTER
以上的默認都是可以修改的哈。
判斷是否登錄使用傳統(tǒng)的cookie session模式哈,JSESSIONID E3512CD1A81DB7F2144C577BA38D2D92 HttpOnly true
自定義配置
實際項目中我們的用戶、密碼、角色、權(quán)限、資源都是存儲在數(shù)據(jù)庫中的,我們可以通過自定義類繼承 WebSecurityConfigurerAdapter,從而實現(xiàn)對 Spring Security 更多的自定義配置。
@Configuration public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter { ... }
配置密碼加密方式
必須配置,否則報空指針異常。
@Bean PasswordEncoder passwordEncoder() { // 不加密 return NoOpPasswordEncoder.getInstance(); }
Spring Security 提供了多種密碼加密方案,官方推薦使用 BCryptPasswordEncoder:
BCryptPasswordEncoder 使用 BCrypt 強哈希函數(shù),開發(fā)者在使用時可以選擇提供 strength 和 SecureRandom 實例。strength 取值在 4~31 之間(默認為 10)。strength 越大,密鑰的迭代次數(shù)越多(密鑰迭代次數(shù)為 2^strength)。如果是數(shù)據(jù)庫認證,庫里的密碼同樣也存放加密后的密碼。同一密碼每次 Bcrypt 生成的結(jié)果都會變化不會重復。
配置AuthenticationManagerBuilder 認證用戶、角色權(quán)限
支持直接配置內(nèi)存認證模式和配置UserDetailsService
Bean方式
內(nèi)存認證模式,實際項目不用這個哦。(僅做了解)
/** * 配置用戶及其對應的角色 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin").password("123").roles("ADMIN", "USER") .and() .withUser("laker").password("123").roles("USER"); }
上面在UserDetailsService的實現(xiàn)之一InMemoryUserDetailsManager中,即存儲在內(nèi)存中。
自定義UserDetailsService
Bean方式,實際項目都是使用這個,可定制化程度高。
步驟一:定義一個LakerUserService
實現(xiàn)UserDetailsService
,配置成SpringBean。該方法將在用戶登錄時自動調(diào)用。
@Service public class LakerUserService implements UserDetailsService { @Autowired UserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // username 就是前端傳遞的例如 laker 123,即 laker User user = userMapper.loadUserByUsername(username); if (user == null) { throw new UsernameNotFoundException("賬戶不存在!"); } user.setAuthorities(...); return user; } }
username
password(加密后的密碼)
authorities
返回的Bean中以上3個都不能為空。返回User后由系統(tǒng)提供的 DaoAuthenticationProvider 類去比對密碼是否正確。
Spring Security默認支持表單請求登錄的源碼,UsernamePasswordAuthenticationFilter.java
步驟二:在把自定義的LakerUserService
裝載進去.
@Autowired UserService userService; ... @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService); }
步驟三:其中我們的業(yè)務用戶User必須要實現(xiàn)UserDetails
接口,并實現(xiàn)該接口中的 7 個方法:
- getAuthorities():獲取當前用戶對象所具有的權(quán)限信息
- getPassword():獲取當前用戶對象的密碼
返回的密碼和用戶輸入的登錄密碼不匹配,會自動拋出 BadCredentialsException 異常。
- getUsername():獲取當前用戶對象的用戶名
- isAccountNonExpired():當前賬戶是否未過期
- isAccountNonLocked():當前賬戶是否未鎖定
- 返回了 false,會自動拋出 AccountExpiredException 異常。
- isCredentialsNonExpired():當前賬戶密碼是否未過期
- isEnabled():當前賬戶是否可用
@Data public class User implements UserDetails { private Integer id; private String username; private String password; private Boolean enabled; private Boolean locked; private List<String> authorities; @Override public String getPassword() { return password; } @Override public String getUsername() { return username; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return !locked; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return enabled; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { List<SimpleGrantedAuthority> authoritiesList = new ArrayList<>(); for (String authority : authorities) { authoritiesList.add(new SimpleGrantedAuthority(authority)); } return authoritiesList; } }
配置HttpSecurity Url訪問權(quán)限
/** * 配置 URL 訪問權(quán)限 */ @Override protected void configure(HttpSecurity http) throws Exception { // http // 1.開啟 HttpSecurity 配置 .authorizeRequests() // laker/** 模式URL必須具備laker.query .antMatchers("/laker/**").hasAnyAuthority("laker.query") // 用戶訪問其它URL都必須認證后訪問(登錄后訪問) .anyRequest().authenticated() .and() // 2.開啟表單登錄,前后端分離的時候不用這個 .formLogin() // 未登錄時 重定向的url 默認是/login 內(nèi)置的頁面,可以自己自定義哈。一般前后端分離,不用這個 // .loginPage("/login") // // .defaultSuccessUrl("/user",true) // .usernameParameter("username") // default is username // .passwordParameter("password") // default is password // .loginPage("/authentication/login") // default is /login with an HTTP get // .failureUrl("/authentication/login?failed") // default is /login?error // .loginProcessingUrl("/authentication/login/process") // default is /login .and() // 3.關(guān)閉csrf,前后端分離不需要這個。 .csrf().disable(); //授權(quán)碼模式需要 會彈出默認自帶的登錄框 http.httpBasic(); // 開啟注銷登錄的配置 http.logout() // 配置注銷登錄請求URL為"/logout"(默認也就是 /logout) .logoutSuccessUrl("/logout") .clearAuthentication(true) // 清除身份認證信息 .invalidateHttpSession(true) // 使 session 失效; }
- formLogin() 表示開啟表單登錄
- **defaultSuccessUrl()**表示默認登錄驗證成功跳轉(zhuǎn)的url,默認重定向到上次訪問未成功的,如果沒有則重定向到
/
. - loginProcessingUrl() 方法配置登錄接口為“/login”,即可以直接調(diào)用“/login”接口,發(fā)起一個 POST 請求進行登錄,登錄參數(shù)中用戶名必須為 username,密碼必須為 password,配置 loginProcessingUrl 接口主要是方便 Ajax 或者移動端調(diào)用登錄接口。
anyRequest | 匹配所有請求路徑
access | SpringEl表達式結(jié)果為true時可以訪問
anonymous | 匿名可以訪問 所有人都能訪問,但是帶上 token訪問后會報錯403
denyAll | 用戶不能訪問 所有人都能訪問,包括帶上 token 訪問
fullyAuthenticated | 用戶完全認證可以訪問(非remember-me下自動登錄)
hasAnyAuthority | 如果有參數(shù),參數(shù)表示權(quán)限,則其中任何一個權(quán)限可以訪問
hasAnyRole | 如果有參數(shù),參數(shù)表示角色,則其中任何一個角色可以訪問
hasAuthority | 如果有參數(shù),參數(shù)表示權(quán)限,則其權(quán)限可以訪問
hasIpAddress | 如果有參數(shù),參數(shù)表示IP地址,如果用戶IP和參數(shù)匹配,則可以訪問
hasRole | 如果有參數(shù),參數(shù)表示角色,則其角色可以訪問
permitAll | 用戶可以任意訪問
rememberMe | 允許通過remember-me登錄的用戶訪問
authenticated | 用戶登錄后可訪問
自定義successHandler
登錄成功后默認是重定向url,我們可以自定義返回json用于前后端分離場景以及其他邏輯,例如成功之后發(fā)短信等。
http.formLogin().successHandler((req, resp, authentication) -> { // 發(fā)短信哈 Object principal = authentication.getPrincipal(); resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(new ObjectMapper().writeValueAsString(principal)); out.flush(); out.close(); })
自定義failureHandler
登錄失敗回調(diào)
http.formLogin().failureHandler((req, resp, e) -> { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write(e.getMessage()); out.flush(); out.close(); })
自定義未認證處理
http.exceptionHandling() .authenticationEntryPoint((req, resp, authException) -> { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write("尚未登錄,請先登錄"); out.flush(); out.close(); } );
自定義權(quán)限不足處理
http.exceptionHandling() //沒有權(quán)限,返回json .accessDeniedHandler((request,response,ex) -> { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter out = response.getWriter(); Map<String,Object> map = new HashMap<String,Object>(); map.put("code",403); map.put("message", "權(quán)限不足"); out.write(objectMapper.writeValueAsString(map)); out.flush(); out.close(); })
自定義注銷登錄
.logout() .logoutUrl("/logout") .logoutSuccessHandler((req, resp, authentication) -> { resp.setContentType("application/json;charset=utf-8"); PrintWriter out = resp.getWriter(); out.write("注銷成功"); out.flush(); out.close(); })
前后端分離場景
上面的都是入門的,實際項目中一般都是前后端分離的,在登錄時都是自定義登錄接口,例如登錄接口是restful風格,增加了其他的驗證碼參數(shù),還使用jwt來完成登錄鑒權(quán)等。
提供登錄接口
該接口需要在配置當中放行,未授權(quán)訪問需要授權(quán)的請求時,會返回401或者403狀態(tài)碼,前端可以根據(jù)這個進行路由提示處理。
@RestController public class LoginController { @Autowired LoginService ... @PostMapping("/login") public login(@RequestBody Login login){ ... return token; } }
Service層創(chuàng)建UsernamePasswordAuthenticationToken
對象,把用戶名和密碼封裝成Authentication
對象.
@Service public class LoginServiceImpl implements LoginService { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Override public doLogin(Login login) { UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password; Authentication authenticate try { // 該方法會去調(diào)用UserDetailsServiceImpl.loadUserByUsername authenticate = authenticationManager.authenticate(authenticationToken); } catch (AuthenticationException e) { e.printStackTrace(); } if (Objects.isNull(authenticate)) { //用戶名密碼錯誤 throw new ServicesException(...); } User authUser = (User) authenticate.getPrincipal(); String token = JwtUtil.createJWT(username); Map<String, String> map = new HashMap<>(); map.put("token", token); return map; } }
自定義認證過濾器
坊間有2種實現(xiàn)方式。
方式一:繼承UsernamePasswordAuthenticationFilter的寫法需要使用登陸成功處理器、失敗處理器等,還是需要按照security這一套來玩。
Spring Security默認支持表單請求登錄的源碼UsernamePasswordAuthenticationFilter.java
方式二:使用Filter的寫法沒有任何限制怎么玩都行,比如說添加其他參數(shù)驗證碼,返回json,token鑒權(quán)等。
@Component public class LakerOncePerRequestFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String token = request.getHeader("Authorization"); if (!StringUtils.isEmpty(token) ) { // 校驗token ... UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user, null, authorities; authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } chain.doFilter(request, response); } } //3、在UsernamePasswordAuthenticationFilter前添加認證過濾器 http.addFilterBefore(lakerOncePerRequestFilter, UsernamePasswordAuthenticationFilter.class);
鑒權(quán)
1.注解鑒權(quán)
- 在
SpringSecurity
配置類中開啟方法級的認證 - 使用
@PreAuthorize
注解在方法或者類
@EnableGlobalMethodSecurity(prePostEnabled = true) public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter { ... @RestController public class Controller { @GetMapping("/hello") @PreAuthorize("hasAnyAuthority('laker.query')") public String test() { }
2.自定義Bean動態(tài)鑒權(quán)
因為@PreAuthorize
支持SpringEL表達式,所以可以支持自定義SpringBean動態(tài)鑒權(quán)。
- 先自定義一個SpringBean。
- 使用
@PreAuthorize
注解在方法或者類配合@PreAuthorize(“@rbacService.hasPermission(‘xx’)”)
@Component("rbacService") public class LakerRBACService { public boolean hasPermission(HttpServletRequest request, Authentication authentication) { Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { UserDetails userDetails=(UserDetails)principal; /** * 該方法主要對比認證過的用戶是否具有請求URL的權(quán)限,有則返回true */ //本次要訪問的資源 SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getMethod() + "" + request.getRequestURI()); //用戶擁有的權(quán)限中是否包含請求的url return userDetails.getAuthorities().contains(simpleGrantedAuthority); } return false; } public boolean hasPermission() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { UserDetails userDetails = (UserDetails) principal; /** * 該方法主要對比認證過的用戶是否具有請求URL的權(quán)限,有則返回true */ //本次要訪問的資源 HttpServletRequest request =((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(request.getRequestURI()); //用戶擁有的權(quán)限中是否包含請求的url return userDetails.getAuthorities().contains(simpleGrantedAuthority); } return false; } } // controller方法 @PreAuthorize("@rbacService.hasPermission()") public String test() { } // 或者高級的全局url鑒權(quán) public class SecurityConfig extends WebSecurityConfigurerAdapter { ... http.authorizeRequests() //設置授權(quán)請求,任何請求都要經(jīng)過下面的權(quán)限表達式處理 .anyRequest().access("@rbacService.hasPermission(request,authentication)") //權(quán)限表達式
3.擴展默認方法自定義擴展根對象SecurityExpressionRoot
http://chabaoo.cn/article/245172.htm
1.創(chuàng)建自定義根對象
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations { public CustomMethodSecurityExpressionRoot(Authentication authentication) { super(authentication); } /** * 自定義表達式 * @param username 具有權(quán)限的用戶賬號 */ public boolean hasUser(String... username) { String name = this.getAuthentication().getName(); HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String[] names = username; for (String nameStr : names) { if (name.equals(nameStr)) { return true; } } return false; } }
2.創(chuàng)建自定義處理器
創(chuàng)建自定義處理器,主要是重寫創(chuàng)建根對象的方法。
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler { @Override protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) { CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication); root.setThis(invocation.getThis()); root.setPermissionEvaluator(getPermissionEvaluator()); root.setTrustResolver(getTrustResolver()); root.setRoleHierarchy(getRoleHierarchy()); root.setDefaultRolePrefix(getDefaultRolePrefix()); return root; } }
3.配置GlobalMethodSecurityConfiguration
之前我們使用@EnableGlobalMethodSecurity開啟全局方法安全,而這些全局方法級別的安全配置就在GlobalMethodSecurityConfiguration配置類中。
可以擴展這個類來自定義默認值,但必須確保在類上指定@EnableGlobalMethodSecurity 注解,否則會bean沖突報錯。
@Configuration // 將EnableGlobalMethodSecurity注解移到這里 @EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { @Override protected MethodSecurityExpressionHandler createExpressionHandler() { return new CustomMethodSecurityExpressionHandler(); } }
4.controller使用自定義方法
@PreAuthorize("hasUser('laker','admin')") public String test() { ... }
登出
http.logout().logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> { // 刪除用戶token ... // 返回json response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_OK); PrintWriter out = response.getWriter(); out.write("OK"); out.flush(); out.close(); });
跨域
@Override protected void configure(HttpSecurity http) throws Exception { http.cors();//允許跨域,配置后SpringSecurity會自動尋找name=corsConfigurationSource的Bean http.csrf().disable();//關(guān)閉CSRF防御 } @Configuration public class CrosConfig { @Bean CorsConfigurationSource corsConfigurationSource() { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration cores=new CorsConfiguration(); cores.setAllowCredentials(true);//允許客戶端攜帶認證信息 //springBoot 2.4.1版本之后,不可以用 * 號設置允許的Origin,如果不降低版本,則在跨域設置時使用setAllowedOriginPatterns方法 // cores.setAllowedOrigins(Collections.singletonList("*"));//允許所有域名可以跨域訪問 cores.setAllowedOriginPatterns(Collections.singletonList("*")); cores.setAllowedMethods(Arrays.asList("GET","POST","DELETE","PUT","UPDATE"));//允許哪些請求方式可以訪問 cores.setAllowedHeaders(Collections.singletonList("*"));//允許服務端訪問的客戶端請求頭 // 暴露哪些頭部信息(因為跨域訪問默認不能獲取全部頭部信息) cores.addExposedHeader(jsonWebTokenUtil.getHeader()); // 注冊跨域配置 // 也可以使用CorsConfiguration 類的 applyPermitDefaultValues()方法使用默認配置 source.registerCorsConfiguration("/**",cores.applyPermitDefaultValues()); return source; } }
全局配置
@EnableGlobalMethodSecurity(prePostEnabled = true) @Configuration public class SpringSecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired UserService userService; @Autowired TokenFilter tokenFilter; /** * 配置 URL 訪問權(quán)限 */ @Override protected void configure(HttpSecurity http) throws Exception { // http // 1.過濾請求 .authorizeRequests() // 2.對于登錄login 驗證碼captcha 允許訪問 .antMatchers("/login").permitAll() // 用戶訪問其它URL都必須認證后訪問(登錄后訪問) .anyRequest().authenticated() .and() // 3.關(guān)閉csrf .csrf().disable() // 4.基于token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 5.頁面能不能以 frame、 iframe、 object 形式嵌套在其他站點中,用來避免點擊劫持(clickjacking)攻擊 .and().headers().frameOptions().disable(); // 異常處理 http.exceptionHandling() // 未認證返回401 .authenticationEntryPoint((req, response, authException) -> { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); PrintWriter out = response.getWriter(); out.write("尚未登錄,請先登錄"); out.flush(); out.close(); }) // 沒有權(quán)限,返回403 json .accessDeniedHandler((request, response, ex) -> { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); PrintWriter out = response.getWriter(); Map<String, Object> map = new HashMap<String, Object>(); map.put("code", 403); map.put("message", "權(quán)限不足"); out.write(JSONUtil.toJsonPrettyStr(map)); out.flush(); out.close(); }); // 配置登出 http.logout().logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> { // 刪除用戶token response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_OK); PrintWriter out = response.getWriter(); out.write("OK"); out.flush(); out.close(); }); // 添加JWT filter http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class); } /** * 配置用戶及其對應的角色 */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userService); } /** * 適用于靜態(tài)資源的防攔截,css、js、image 等文件 * 配置的url不會保護它們免受CSRF、XSS、Clickjacking等的影響。 * 相反,如果您想保護端點免受常見漏洞的侵害,請參閱configure(HttpSecurity) */ @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**"); } @Bean PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 解決 無法直接注入 AuthenticationManager * */ @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
參考:
https://blog.csdn.net/X_lsod/article/details/122914659
https://blog.csdn.net/godleaf/article/details/108318403
https://blog.csdn.net/qq_43437874/article/details/119543579
到此這篇關(guān)于SpringBoot Security從入門到實戰(zhàn)示例教程的文章就介紹到這了,更多相關(guān)SpringBoot Security入門內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringSecurity導致SpringBoot跨域失效的問題解決
- SpringBoot?SpringSecurity?JWT實現(xiàn)系統(tǒng)安全策略詳解
- SpringBoot整合Security權(quán)限控制登錄首頁
- SpringBoot?整合Security權(quán)限控制的初步配置
- SpringBoot?Security使用MySQL實現(xiàn)驗證與權(quán)限管理
- SpringBoot淺析安全管理之Spring Security配置
- SpringBoot+SpringSecurity+jwt實現(xiàn)驗證
- Springboot詳解整合SpringSecurity實現(xiàn)全過程
- SpringBoot Security實現(xiàn)單點登出并清除所有token
相關(guān)文章
Java超詳細精講數(shù)據(jù)結(jié)構(gòu)之bfs與雙端隊列
廣搜BFS的基本思想是: 首先訪問初始點v并將其標志為已經(jīng)訪問。接著通過鄰接關(guān)系將鄰接點入隊。然后每訪問過一個頂點則出隊。按照順序,訪問每一個頂點的所有未被訪問過的頂點直到所有的頂點均被訪問過。廣度優(yōu)先遍歷類似與層次遍歷2022-07-07Java的JSON轉(zhuǎn)換類庫GSON的基礎使用教程
GSON是谷歌開源的一款Java對象與JSON對象互相轉(zhuǎn)換的類庫,Java的JSON轉(zhuǎn)換類庫GSON的基礎使用教程,需要的朋友可以參考下2016-06-06Java 使用JdbcTemplate 中的queryForList發(fā)生錯誤解決辦法
這篇文章主要介紹了Java 使用JdbcTemplate 中的queryForList發(fā)生錯誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-07-07Spring?@DateTimeFormat日期格式化時注解場景分析
這篇文章主要介紹了Spring?@DateTimeFormat日期格式化時注解場景分析,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05