1. 程式人生 > 資料庫 >spring boot + spring Security + redis + token 爬坡

spring boot + spring Security + redis + token 爬坡

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的身份驗證,新增UserDetailsS​​ervice以及新增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等獲取