摘要:進(jìn)行下一項(xiàng)配置,為了區(qū)分必須加入。另起一行,以示尊重。這行代碼主要是用于驗(yàn)證,后面再說(shuō)。然后跑下接口,發(fā)現(xiàn)沒(méi)問(wèn)題,正常打印,說(shuō)明主體也在上下文中了。說(shuō)明這會(huì)上下文環(huán)境中我們主體不存在。所說(shuō)以,主體數(shù)據(jù)生命周期是一次請(qǐng)求。
原來(lái)一直使用shiro做安全框架,配置起來(lái)相當(dāng)方便,正好有機(jī)會(huì)接觸下SpringSecurity,學(xué)習(xí)下這個(gè)。順道結(jié)合下jwt,把安全信息管理的問(wèn)題扔給客戶(hù)端,準(zhǔn)備
首先用的是SpringBoot,省去寫(xiě)各種xml的時(shí)間。然后把依賴(lài)加入一下
org.springframework.boot spring-boot-starter-security io.jsonwebtoken jjwt 0.9.1
application.yml加上一點(diǎn)配置信息,后面會(huì)用
jwt: secret: secret expiration: 7200000 token: Authorization
可能用到代碼,目錄結(jié)構(gòu)放出來(lái)一下
配置 SecurityConfig配置首先是配置SecurityConfig,代碼如下
@Configuration @EnableWebSecurity// 這個(gè)注解必須加,開(kāi)啟Security @EnableGlobalMethodSecurity(prePostEnabled = true)//保證post之前的注解可以使用 public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired JwtUserDetailsService jwtUserDetailsService; @Autowired JwtAuthorizationTokenFilter authenticationTokenFilter; //先來(lái)這里認(rèn)證一下 @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoderBean()); } //攔截在這配 @Override protected void configure(HttpSecurity http) throws Exception { http .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .authorizeRequests() .antMatchers("/login").permitAll() .antMatchers("/haha").permitAll() .antMatchers("/sysUser/test").permitAll() .antMatchers(HttpMethod.OPTIONS, "/**").anonymous() .anyRequest().authenticated() // 剩下所有的驗(yàn)證都需要驗(yàn)證 .and() .csrf().disable() // 禁用 Spring Security 自帶的跨域處理 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 定制我們自己的 session 策略:調(diào)整為讓 Spring Security 不創(chuàng)建和使用 session http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } @Bean public PasswordEncoder passwordEncoderBean() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
ok,下面娓娓道來(lái)。首先我們這個(gè)配置類(lèi)繼承了WebSecurityConfigurerAdapter,這里面有三個(gè)重要的方法需要我們重寫(xiě)一下:
configure(HttpSecurity http):這個(gè)方法是我們配置攔截的地方,exceptionHandling().authenticationEntryPoint(),這里面主要配置如果沒(méi)有憑證,可以進(jìn)行一些操作,這個(gè)后面會(huì)看jwtAuthenticationEntryPoint這個(gè)里面的代碼。進(jìn)行下一項(xiàng)配置,為了區(qū)分必須加入.and()。authorizeRequests()這個(gè)后邊配置那些路徑有需要什么權(quán)限,比如我配置的那幾個(gè)url都是permitAll(),及不需要權(quán)限就可以訪(fǎng)問(wèn)。值得一提的是antMatchers(HttpMethod.OPTIONS, "/**"),是為了方便后面寫(xiě)前后端分離的時(shí)候前端過(guò)來(lái)的第一次驗(yàn)證請(qǐng)求,這樣做,會(huì)減少這種請(qǐng)求的時(shí)間和資源使用。csrf().disable()是為了防止csdf攻擊的,至于什么是csdf攻擊,請(qǐng)自行百度。
另起一行,以示尊重。sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);因?yàn)槲覀円褂胘wt托管安全信息,所以把Session禁止掉??聪耂essionCreationPolicy枚舉的幾個(gè)參數(shù):
public enum SessionCreationPolicy { ALWAYS,//總是會(huì)新建一個(gè)Session。 NEVER,//不會(huì)新建HttpSession,但是如果有Session存在,就會(huì)使用它。 IF_REQUIRED,//如果有要求的話(huà),會(huì)新建一個(gè)Session。 STATELESS;//這個(gè)是我們用的,不會(huì)新建,也不會(huì)使用一個(gè)HttpSession。 private SessionCreationPolicy() { } }
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);這行代碼主要是用于JWT驗(yàn)證,后面再說(shuō)。
configure(WebSecurity web):這個(gè)方法我代碼中沒(méi)有用,這個(gè)方法主要用于訪(fǎng)問(wèn)一些靜態(tài)的東西控制。其中ignoring()方法可以讓訪(fǎng)問(wèn)跳過(guò)filter驗(yàn)證。
configureGlobal(AuthenticationManagerBuilder auth):這個(gè)方法是主要進(jìn)行驗(yàn)證的地方,其中jwtUserDetailsService代碼待會(huì)會(huì)看,passwordEncoder(passwordEncoderBean())是密碼的一種加密方式。
還有兩個(gè)注解:@EnableWebSecurity,這個(gè)注解必須加,開(kāi)啟Security。
@EnableGlobalMethodSecurity(prePostEnabled = true),保證post之前的注解可以使用
以上,我們可以確定了哪些路徑訪(fǎng)問(wèn)不需要任何權(quán)限了,至于哪些路徑需要什么權(quán)限接著往下看。
SecurityUserDetailsSecurity 中也有類(lèi)似于shiro中主體的概念,就是在內(nèi)存中存了一個(gè)東西,方便程序判斷當(dāng)前請(qǐng)求的用戶(hù)有什么權(quán)限,需要實(shí)現(xiàn)UserDetails這個(gè)接口,所以我寫(xiě)了這個(gè)類(lèi),并且繼承了我自己的類(lèi)SysUser。
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) public class SecurityUserDetails extends SysUser implements UserDetails { private Collection extends GrantedAuthority> authorities; @Override public Collection extends GrantedAuthority> getAuthorities() { return authorities; } public SecurityUserDetails(String userName, Collection extends GrantedAuthority> authorities){ this.authorities = authorities; this.setUsername(userName); String encode = new BCryptPasswordEncoder().encode("123456"); this.setPassword(encode); this.setAuthorities(authorities); } /** * 賬戶(hù)是否過(guò)期 * @return */ @Override public boolean isAccountNonExpired() { return true; } /** * 是否禁用 * @return */ @Override public boolean isAccountNonLocked() { return true; } /** * 密碼是否過(guò)期 * @return */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 是否啟用 * @return */ @Override public boolean isEnabled() { return true; } }
authorities就是我們的權(quán)限,構(gòu)造方法中我手動(dòng)把密碼set進(jìn)去了,這不合適,包括權(quán)限我也是手動(dòng)傳進(jìn)去的。這些東西都應(yīng)該從數(shù)據(jù)庫(kù)搜出來(lái),我現(xiàn)在只是體驗(yàn)一把Security,角色權(quán)限那一套都沒(méi)寫(xiě),所以說(shuō)明一下就好了,這個(gè)構(gòu)造方法就是傳進(jìn)來(lái)一個(gè)標(biāo)志(我這里用的是username,或者應(yīng)該用userId什么的都可以),然后給你一個(gè)完整的主體信息,供其他地方使用。ok,next。
JwtUserDetailsServiceSecurityConfig配置里面不是有個(gè)方法是做真正的認(rèn)證嘛,或者說(shuō)從數(shù)據(jù)庫(kù)拿信息,具體那認(rèn)證信息的方法就是在這個(gè)方法里面。
@Service public class JwtUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String user) throws UsernameNotFoundException { System.out.println("JwtUserDetailsService:" + user); ListauthorityList = new ArrayList<>(); authorityList.add(new SimpleGrantedAuthority("ROLE_USER")); return new SecurityUserDetails(user,authorityList); } }
繼承了Security提供的UserDetailsService接口,實(shí)現(xiàn)loadUserByUsername這個(gè)方法,我們這里手動(dòng)模擬從數(shù)據(jù)庫(kù)搜出來(lái)一個(gè)叫USER的權(quán)限,通過(guò)剛才的構(gòu)造方法,模擬生成當(dāng)前user的信息,供后面jwt Filter一大堆驗(yàn)證。至于為什么USER權(quán)限要加上“ROLE_”前綴,待會(huì)會(huì)說(shuō)。
ok,現(xiàn)在我們知道了怎么配置各種url是否需要權(quán)限才能訪(fǎng)問(wèn),也知道了哪里可以拿到我們的主體信息,那么繼續(xù)。
JwtAuthorizationTokenFilter千呼萬(wàn)喚始出來(lái),JWT終于可以上場(chǎng)了。至于怎么生成這個(gè)token憑證,待會(huì)會(huì)說(shuō),現(xiàn)在假設(shè)前端已經(jīng)拿到了token憑證,要訪(fǎng)問(wèn)某個(gè)接口了,看看怎么進(jìn)行jwt業(yè)務(wù)的攔截吧。
@Component public class JwtAuthorizationTokenFilter extends OncePerRequestFilter { private final UserDetailsService userDetailsService; private final JwtTokenUtil jwtTokenUtil; private final String tokenHeader; public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.token}") String tokenHeader) { this.userDetailsService = userDetailsService; this.jwtTokenUtil = jwtTokenUtil; this.tokenHeader = tokenHeader; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String requestHeader = request.getHeader(this.tokenHeader); String username = null; String authToken = null; if (requestHeader != null && requestHeader.startsWith("Bearer ")) { authToken = requestHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(authToken); } catch (ExpiredJwtException e) { } } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } } chain.doFilter(request, response); } }
提前說(shuō)一下,關(guān)于@Value注解參數(shù)開(kāi)頭寫(xiě)了。
doFilterInternal() 這個(gè)方法就是這個(gè)過(guò)濾器的精髓了。首先從header中獲取憑證authToken,從中挖掘出來(lái)我們的username,然后看看上下文中是否有我們以這個(gè)username為標(biāo)識(shí)的主體。沒(méi)有,ok,去new一個(gè)(如果對(duì)象也可以new就好了。。。)。然后就是驗(yàn)證這個(gè)authToken 是否在有效期呢啊,驗(yàn)證token是否對(duì)啊等等吧。其實(shí)我們剛剛把我們SecurityUserDetails這個(gè)對(duì)象叫做主體,到這里我才發(fā)現(xiàn)有點(diǎn)自做多情了,因?yàn)樯蒘ecurity承認(rèn)的主體是通過(guò)UsernamePasswordAuthenticationToken類(lèi)似與這種類(lèi)去實(shí)現(xiàn)的,之前之所以叫SecurityUserDetails為主體,只是它存了一些關(guān)鍵信息。然后將主體信息————authentication,存入上下文環(huán)境,供后面使用。
我的很多工具類(lèi)代碼都放到了jwtTokenUtil,下面貼一下代碼:
@Component public class JwtTokenUtil implements Serializable { private static final long serialVersionUID = -3301605591108950415L; @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; @Value("${jwt.token}") private String tokenHeader; private Clock clock = DefaultClock.INSTANCE; public String generateToken(UserDetails userDetails) { Mapclaims = new HashMap<>(); return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map claims, String subject) { final Date createdDate = clock.now(); final Date expirationDate = calculateExpirationDate(createdDate); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(createdDate) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } private Date calculateExpirationDate(Date createdDate) { return new Date(createdDate.getTime() + expiration); } public Boolean validateToken(String token, UserDetails userDetails) { SecurityUserDetails user = (SecurityUserDetails) userDetails; final String username = getUsernameFromToken(token); return (username.equals(user.getUsername()) && !isTokenExpired(token) ); } public String getUsernameFromToken(String token) { return getClaimFromToken(token, Claims::getSubject); } public T getClaimFromToken(String token, Function claimsResolver) { final Claims claims = getAllClaimsFromToken(token); return claimsResolver.apply(claims); } private Claims getAllClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(clock.now()); } public Date getExpirationDateFromToken(String token) { return getClaimFromToken(token, Claims::getExpiration); } }
根據(jù)注釋你能猜個(gè)大概吧,就不再說(shuō)了,有些東西是jwt方面的東西,今天就不再多說(shuō)了。
JwtAuthenticationEntryPoint前面還說(shuō)了一個(gè)發(fā)現(xiàn)沒(méi)有憑證走一個(gè)方法,代碼也貼一下。
@Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { System.out.println("JwtAuthenticationEntryPoint:"+authException.getMessage()); response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"沒(méi)有憑證"); } }
實(shí)現(xiàn)AuthenticationEntryPoint這個(gè)接口,發(fā)現(xiàn)沒(méi)有憑證,往response中放些東西。
run code下面跑一下幾個(gè)接口,看看具體是怎么具體訪(fǎng)問(wèn)某個(gè)方法的吧,還有前面一點(diǎn)懸念一并解決。
登錄先登錄一下,看看怎么生成token扔給前端的吧。
@RestController public class LoginController { @Autowired @Qualifier("jwtUserDetailsService") private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @PostMapping("/login") public String login(@RequestBody SysUser sysUser, HttpServletRequest request){ final UserDetails userDetails = userDetailsService.loadUserByUsername(sysUser.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails); return token; } @PostMapping("haha") public String haha(){ UserDetails userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return "haha:"+userDetails.getUsername()+","+userDetails.getPassword(); } }
我們前面配置中已經(jīng)把login設(shè)置為隨便訪(fǎng)問(wèn)了,這邊通過(guò)jwt生成一個(gè)token串,具體方法請(qǐng)看jwtTokenUtil.generateToken,已經(jīng)寫(xiě)了。只要知道這里面存了username、加密規(guī)則、過(guò)期時(shí)間就好了。
然后跑下haha接口,發(fā)現(xiàn)沒(méi)問(wèn)題,正常打印,說(shuō)明主體也在上下文中了。
需要權(quán)限然后我們?cè)L問(wèn)一個(gè)需要權(quán)限的接口吧。
@RestController @RequestMapping("/sysUser") public class SysUserController { @GetMapping(value = "/test") public String test() { return "Hello Spring Security"; } @PreAuthorize("hasAnyRole("USER")") @PostMapping(value = "/testNeed") public String testNeed() { return "testNeed"; } }
訪(fǎng)問(wèn)testNeed接口,看到?jīng)],@PreAuthorize("hasAnyRole("USER")")這個(gè)說(shuō)明需要USER權(quán)限!我們?cè)趧倓偵蒘ecurityUserDetails這個(gè)的時(shí)候已經(jīng)模擬加入了USER權(quán)限了,所以可以訪(fǎng)問(wèn)?,F(xiàn)在說(shuō)說(shuō)為什么加權(quán)限的時(shí)候需要加入前綴“ROLE_”.看hasAnyRole源碼:
public final boolean hasAnyRole(String... roles) { return hasAnyAuthorityName(defaultRolePrefix, roles); } private boolean hasAnyAuthorityName(String prefix, String... roles) { SetroleSet = getAuthoritySet(); for (String role : roles) { String defaultedRole = getRoleWithDefaultPrefix(prefix, role); if (roleSet.contains(defaultedRole)) { return true; } } return false; } private static String getRoleWithDefaultPrefix(String defaultRolePrefix, String role) { if (role == null) { return role; } if (defaultRolePrefix == null || defaultRolePrefix.length() == 0) { return role; } if (role.startsWith(defaultRolePrefix)) { return role; } return defaultRolePrefix + role; } 關(guān)鍵是 defaultRolePrefix 看這個(gè)類(lèi)最上面 private String defaultRolePrefix = "ROLE_";
人家源碼這么干的,咱們就這么寫(xiě)唄,咱也不敢問(wèn)。其實(shí)也有不需要前綴的方式,去看看SecurityExpressionRoot這個(gè)類(lèi)吧,用的方法不一樣,也就是@PreAuthorize里面有另外一個(gè)參數(shù)。
一個(gè)重要的問(wèn)題先說(shuō)結(jié)論:Security上下文環(huán)境(里面有主體)生命周期只限于一次請(qǐng)求。
我做了一個(gè)測(cè)試:
把SecurityConfig里面configure(HttpSecurity http)這個(gè)方法里面
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
這行代碼注釋掉,不走那個(gè)jwt filter。就是不每次都添加上下上下文環(huán)境。
然后loginController改成
@RestController public class LoginController { @Autowired @Qualifier("jwtUserDetailsService") private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @PostMapping("/login") public String login(@RequestBody SysUser sysUser, HttpServletRequest request){ final UserDetails userDetails = userDetailsService.loadUserByUsername(sysUser.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails); //添加 start UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); //添加 end return token; } @PostMapping("haha") public String haha(){ UserDetails userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return "haha:"+userDetails.getUsername()+","+userDetails.getPassword(); } }
然后登陸,然后訪(fǎng)問(wèn)/haha,崩了,發(fā)現(xiàn)userDetails里面沒(méi)數(shù)據(jù)。說(shuō)明這會(huì)上下文環(huán)境中我們主體不存在。
為什么會(huì)這樣呢?
SecurityContextPersistenceFilter 一次請(qǐng)求,filter鏈結(jié)束之后 會(huì)清除掉Context里面的東西。所說(shuō)以,主體數(shù)據(jù)生命周期是一次請(qǐng)求。
源碼如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { ...假裝有一堆代碼... try { } finally { SecurityContext contextAfterChainExecution = SecurityContextHolder .getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything // else. SecurityContextHolder.clearContext(); repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); } }
關(guān)鍵就是finally里面 SecurityContextHolder.clearContext(); 這句話(huà)。這才體現(xiàn)了那句,把維護(hù)信息的事扔給了客戶(hù)端,你不請(qǐng)求,我也不知道你有啥。
體驗(yàn)小結(jié)配置起來(lái)感覺(jué)還可以吧,使用jwt方式,生成token.由于上下文環(huán)境的生命周期是一次請(qǐng)求,所以在不請(qǐng)求的情況下,服務(wù)端不清楚用戶(hù)有那些權(quán)限,真正實(shí)現(xiàn)了客戶(hù)端維護(hù)安全信息,所以項(xiàng)目中也沒(méi)有登出接口,因?yàn)闆](méi)必要。即使前端退出了,你有token,依然可以通過(guò)postman請(qǐng)求接口(token沒(méi)有過(guò)期)。不同于shiro可以把信息維護(hù)在服務(wù)端,要是登出,clear主體信息,訪(fǎng)問(wèn)接口就需要在登錄。不過(guò)Security這樣也有好處,可以實(shí)現(xiàn)單點(diǎn)登陸了,也方便做分布式。(只要你不同子系統(tǒng)中驗(yàn)證那一套邏輯相同,或者在分布式的情況下有多帶帶的驗(yàn)證系統(tǒng))。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74734.html
摘要:如果全部使用默認(rèn)值的情況話(huà)不需要做任何配置方式前提項(xiàng)目需要添加數(shù)據(jù)源依賴(lài)。獲取通過(guò)獲取啟用在使用格式化時(shí)非常簡(jiǎn)單的,配置如下所示開(kāi)啟轉(zhuǎn)換轉(zhuǎn)換時(shí)所需加密,默認(rèn)為恒宇少年于起宇默認(rèn)不啟用,簽名建議進(jìn)行更換。 ApiBoot是一款基于SpringBoot1.x,2.x的接口服務(wù)集成基礎(chǔ)框架, 內(nèi)部提供了框架的封裝集成、使用擴(kuò)展、自動(dòng)化完成配置,讓接口開(kāi)發(fā)者可以選著性完成開(kāi)箱即...
摘要:開(kāi)公眾號(hào)差不多兩年了,有不少原創(chuàng)教程,當(dāng)原創(chuàng)越來(lái)越多時(shí),大家搜索起來(lái)就很不方便,因此做了一個(gè)索引幫助大家快速找到需要的文章系列處理登錄請(qǐng)求前后端分離一使用完美處理權(quán)限問(wèn)題前后端分離二使用完美處理權(quán)限問(wèn)題前后端分離三中密碼加鹽與中異常統(tǒng)一處理 開(kāi)公眾號(hào)差不多兩年了,有不少原創(chuàng)教程,當(dāng)原創(chuàng)越來(lái)越多時(shí),大家搜索起來(lái)就很不方便,因此做了一個(gè)索引幫助大家快速找到需要的文章! Spring Boo...
摘要:什么是是一個(gè)能夠?yàn)榛诘钠髽I(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪(fǎng)問(wèn)控制解決方案的安全框架。它來(lái)自于,那么它與整合開(kāi)發(fā)有著天然的優(yōu)勢(shì),目前與對(duì)應(yīng)的開(kāi)源框架還有。通常大家在做一個(gè)后臺(tái)管理的系統(tǒng)的時(shí)候,應(yīng)該采用判斷用戶(hù)是否登錄。 ? 什么是SpringSecurity ? ? Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪(fǎng)問(wèn)控制解決方案的安全...
摘要:前言現(xiàn)在的好多項(xiàng)目都是基于移動(dòng)端以及前后端分離的項(xiàng)目,之前基于的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線(xiàn),尤其是當(dāng)基于的微服務(wù)架構(gòu)以及單頁(yè)面應(yīng)用流行起來(lái)后,情況更甚。使用生成是什么請(qǐng)自行百度。 1、前言 現(xiàn)在的好多項(xiàng)目都是基于A(yíng)PP移動(dòng)端以及前后端分離的項(xiàng)目,之前基于Session的前后端放到一起的項(xiàng)目已經(jīng)慢慢失寵并淡出我們視線(xiàn),尤其是當(dāng)基于SpringCloud的微服務(wù)架構(gòu)以及...
閱讀 1224·2021-10-20 13:48
閱讀 2279·2021-09-30 09:47
閱讀 3169·2021-09-28 09:36
閱讀 2409·2019-08-30 15:56
閱讀 1241·2019-08-30 15:52
閱讀 2061·2019-08-30 10:48
閱讀 650·2019-08-29 15:04
閱讀 614·2019-08-29 12:54