spring boot + spring Security + redis + token 爬坡
阿新 • • 發佈:2020-12-20
spring boot + spring Security + redis + token 爬坡
分為幾個部分 spring boot 基本配置 controller介面部分 安全校驗部分(包括session或者自定義token的形式) redis的token存放於取出 , 在資料庫取使用者資訊
spring boot 基本配置
pom和啟動類
pom的jar版本要一一對應,不要產生spring衝突
application.yml 和 properties 配置
like this
實現檢驗思路
Security 部分
兩種思路,
第一種
一種是spring Security只負責校驗 ,生成和儲存token的部分放在controller裡
這種情況只需要
WebSecurityConfigurer extends WebSecurityConfigurerAdapter configure: http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); // JWT Filter 校驗部分 在登入的介面配置許可權 @PreAuthorize("hasAuthority('ddd:list')")
filter部分
package com.lzw.security.filter; import com.alibaba.fastjson.JSON; import com.lzw.security.common.GenericResponse; import com.lzw.security.common.ServiceError; import com.lzw.security.entity.User; import com.lzw.security.service.SelfUserDetailsService; import com.lzw.security.util.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Set; /** * @author: jamesluozhiwei 組2 * @description: 確保在一次請求只通過一次filter,而不需要重複執行 被springboot security 主類 呼叫 */ @Component @Slf4j public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Value("${token.expirationMilliSeconds}") private long expirationMilliSeconds; @Autowired SelfUserDetailsService userDetailsService; @Autowired RedisUtil redisUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //? String authHeader = request.getHeader("Authorization"); response.setCharacterEncoding("utf-8"); if (null == authHeader || !authHeader.startsWith("Bearer ")){ filterChain.doFilter(request,response);//token格式不正確 return; } String authToken = authHeader.substring("Bearer ".length()); String subject = JwtTokenUtil.parseToken(authToken);//獲取在token中自定義的subject,用作使用者標識,用來獲取使用者許可權 //獲取redis中的token資訊 if (!redisUtil.hasKey(authToken)){ //token 不存在 返回錯誤資訊 response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN))); return; } //獲取快取中的資訊(根據自己的業務進行拓展) HashMap<String,Object> hashMap = (HashMap<String, Object>) redisUtil.hget(authToken); //從tokenInfo中取出使用者資訊 ******** User user = new User(); user.setId(Long.parseLong(hashMap.get("id").toString())).setAuthorities((Set<? extends GrantedAuthority>) hashMap.get("authorities")); if (null == hashMap){ //使用者資訊不存在或轉換錯誤,返回錯誤資訊 response.getWriter().write(JSON.toJSONString(GenericResponse.response(ServiceError.GLOBAL_ERR_NO_SIGN_IN))); return; } //更新token過期時間 redisUtil.setKeyExpire(authToken,expirationMilliSeconds); //將資訊交給security ******* UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response); } }
第二種
另一種全部託管於spring Security
mian class
WebSecurityConfigurer extends WebSecurityConfigurerAdapter
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(customUserDetailsService) .passwordEncoder(passwordEncoder()); } 設定查詢方法 customUserDetailsService: //資料庫查資訊實現 UserDetailsService @Component public class CustomUserDetailsService implements UserDetailsService passwordEncoder() @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } 把放入token到redis的任務賦給handler public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private RedisTemplate redisTemplate; private ObjectMapper objectMapper = new ObjectMapper(); @SneakyThrows @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){ String token; Long userId = 0l; if(authentication.getPrincipal() instanceof CustomUserDetailsUser){ // CustomUserDetailsUser userDetailsUser = (CustomUserDetailsUser) authentication.getPrincipal(); token = SecureUtil.md5(userDetailsUser.getUsername() + System.currentTimeMillis()); userId = userDetailsUser.getUserId(); }else { token = SecureUtil.md5(String.valueOf(System.currentTimeMillis())); } //redis放token redisTemplate.opsForValue().set(Constant.AUTHENTICATION_TOKEN + token,token,Constant.TOKEN_EXPIRE, TimeUnit.SECONDS); redisTemplate.opsForValue().set(token,userId,Constant.TOKEN_EXPIRE, TimeUnit.SECONDS); response.setCharacterEncoding(CharsetUtil.UTF_8); //網頁頭 response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); //登陸成功了加token到瀏覽器 ? PrintWriter printWriter = response.getWriter(); printWriter.append(objectMapper.writeValueAsString(R.ok().put(Constant.TOKEN,token))); }
在filter裡
先把token和使用者許可權存放到redis 並且 public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { ... @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { ... UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request,response);
附贈小提示
controller介面部分
@RestController/Controller 注入 @RequestMapping 等 獲取請求與返回
spring PreAuthorize 配置
@RequestMapping("/info/{id}") 引數restful @PreAuthorize("hasRole('sys:config:info')") 許可權校驗 public R info(@PathVariable("id") Long id){ SysConfig config = sysConfigService.getById(id); return R.ok().put("config", config); }
value: 指定請求的實際地址, 比如 /action/info之類。 method: 指定請求的method型別, GET、POST、PUT、DELETE等 consumes: 指定處理請求的提交內容型別(Content-Type),例如application/json, text/html; produces: 指定返回的內容型別,僅當request請求頭中的(Accept)型別中包含該指定型別才返回 params: 指定request中必須包含某些引數值是,才讓該方法處理 headers: 指定request中必須包含某些指定的header值,才能讓該方法處理請求
R: 一個vo
post 請求
獲取值
可以用string vo形參和httprequest獲取
spring security 爬坡
主要類分析
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter
Security主體類 功能實現基礎
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
自定義安全校驗 --> AuthenticationManagerBuilder
用於建立AuthenticationManager的SecurityBuilder。允許輕鬆構建記憶體身份驗證,LDAP身份驗證,基於JDBC的身份驗證,新增UserDetailsService以及新增AuthenticationProvider。
auth.userDetailsService(customUserDetailsService) customUserDetailsService 這是一個繼承UserDetailsService 的Component 重寫方法loadUserByUsername 在資料庫裡校驗身份
protected void configure(HttpSecurity http) { 過濾的主方法,核心
antMatchers(urls[String 陣列]) 放行的url 陣列
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) 關閉session
csrf()
Adds CSRF support. This is activated by default when using WebSecurityConfigurerAdapter's default constructor. You can disable it using: @Configuration @EnableWebSecurity public class CsrfSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() ...; } } Returns: the CsrfConfigurer for further customizations Throws: java.lang.Exception
! 在所有filter 之前
addFilterBefore public HttpSecurity addFilterBefore(javax.servlet.Filter filter, java.lang.Class<? extends javax.servlet.Filter> beforeFilter) Description copied from interface: HttpSecurityBuilder Allows adding a Filter before one of the known Filter classes. The known Filter instances are either a Filter listed in HttpSecurityBuilder.addFilter(Filter) or a Filter that has already been added using HttpSecurityBuilder.addFilterAfter(Filter, Class) or HttpSecurityBuilder.addFilterBefore(Filter, Class). Specified by: addFilterBefore in interface HttpSecurityBuilder<HttpSecurity> Parameters: filter - the Filter to register before the type beforeFilter beforeFilter - the Class of the known Filter. Returns: the HttpSecurity for further customizations
這個是所有的訪問都要過一下在這加過濾token的code
@SneakyThrows
lombok 甩出所有的錯誤
@EnableWebSecurity
該註解其實起到了如下效果 : 控制Spring Security是否使用除錯模式(通過註解屬性debug指定),預設為false,表示預設不使用除錯模式; 匯入 WebSecurityConfiguration,用於配置Web安全過濾器FilterChainProxy; 若干個WebSecurityConfigurerAdapter作用於一個WebSecurity生成一個最終使用的web安全過濾器FilterChainProxy 如果是Servlet 環境,匯入WebMvcSecurityConfiguration; 如果是OAuth2環境,匯入OAuth2ClientConfiguration; 使用註解@EnableGlobalAuthentication啟用全域性認證機制 ———————————————— 版權宣告:本文為CSDN博主「安迪源文」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。 原文連結:https://blog.csdn.net/andy_zhang2007/article/details/90023901
關於 @EnableGlobalMethodSecurity 開啟spring
@bean
注入例項到spring 工廠中以便於@Autowired等獲取