欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springSecurity自定義登錄接口和JWT認(rèn)證過(guò)濾器的流程

 更新時(shí)間:2024年12月12日 10:33:56   作者:機(jī)躍  
這篇文章主要介紹了springSecurity自定義登陸接口和JWT認(rèn)證過(guò)濾器的相關(guān)資料,本文給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

下面我會(huì)根據(jù)該流程圖去自定義接口:

我們需要做的任務(wù)有:

 登陸:1、通過(guò)ProviderManager的方法進(jìn)行認(rèn)證,生成jwt;2、把用戶(hù)信息存入redis;3、自定義UserDetailsService實(shí)現(xiàn)到數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù)的方法。

校驗(yàn):自定義一個(gè)jwt認(rèn)證過(guò)濾器,其實(shí)現(xiàn)功能:獲取token;解析token;從redis獲取信息;存入SecurityContextHolder。

登陸:

圖中的 5.1步驟是到內(nèi)存中查詢(xún)用戶(hù)信息,而我們需要的是到數(shù)據(jù)庫(kù)中查詢(xún)。而圖中查詢(xún)用戶(hù)信息是調(diào)用loadUserbyUsername方法實(shí)現(xiàn)的。

所以我們需要實(shí)現(xiàn)UserDetailsService接口并重寫(xiě)該方法:(下面案例中我用的mybatis plus實(shí)現(xiàn)的查詢(xún)數(shù)據(jù)庫(kù))

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;
    //loadUserByUsername方法即為流程圖中查詢(xún)用戶(hù)信息的方法。
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查詢(xún)用戶(hù)信息
        LambdaQueryWrapper<User> queryWrapper= new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUserName,username);
        User user = userMapper.selectOne(queryWrapper);
        //如果沒(méi)有查詢(xún)到用戶(hù)
        if(Objects.isNull(user)){
            throw new RuntimeException("用戶(hù)名或密碼錯(cuò)誤");
        }
        //封裝為UserDetails類(lèi)型返回
        return new LoginUser(user);
    }
}

我們先寫(xiě)好登陸功能的controller層代碼:

@RestController
public class LoginController {
    @Autowired
    private LoginService loginService;
    @PostMapping("/user/login")
    public ResponseResult login(@RequestBody User user){
        //登陸
        return loginService.login(user);
    }

 我們需要讓springSecurity對(duì)該登陸接口放行,不需要登陸就能訪問(wèn)。在登陸service層接口中需要通過(guò)AuthenticationManager的authenticate方法進(jìn)行用戶(hù)認(rèn)證,我們先在SecurityConfig中把AuthenticationManager注入容器。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    @Bean //(name = "") //獲取AuthenticationManager的bean,因?yàn)楝F(xiàn)在只有這一個(gè)AuthenticationManager,所以不寫(xiě)也沒(méi)事。
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
    //放開(kāi)接口
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //關(guān)閉csrf,csrf為跨域策略,不支持post
                .csrf().disable()
                //不通過(guò)session獲取SecurityContext 前后端分離時(shí)session不可用
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                //對(duì)于登陸接口,允許匿名訪問(wèn),登陸之后不允許訪問(wèn),只允許匿名的用戶(hù),可以防止重復(fù)登陸。
                .antMatchers("/user/login").anonymous() //permitAll() 登錄能訪問(wèn),不登錄也能訪問(wèn),一般用于靜態(tài)資源js等
                //除了上面外,所有請(qǐng)求需要鑒權(quán)認(rèn)證
                .anyRequest().authenticated();//authenticated():任意用戶(hù),認(rèn)證后都可訪問(wèn)。
    }
}

然后我們?nèi)バ薷牡顷懡涌诘膕ervice層實(shí)現(xiàn)類(lèi)代理:

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;
    //登陸
    @Override
    public ResponseResult login(User user) {
        //獲取AuthenticationManager的authenticate方法進(jìn)行認(rèn)證。
        //通過(guò)SecurityConfig獲取AuthenticationManager
        //創(chuàng)建Authentication,第一個(gè)參數(shù)為認(rèn)證主體,沒(méi)有的話傳用戶(hù)名,第二個(gè)參數(shù)傳密碼
        UsernamePasswordAuthenticationToken authenticationToken
                =new UsernamePasswordAuthenticationToken(user.getUserName(),user.getPassword());
        //需要Authentication參數(shù)(上面)
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        //這樣讓ProviderManager調(diào)用UserDetailsService類(lèi)中的loadUserByUsername方法完成認(rèn)證
        //如果認(rèn)證不通過(guò),authenticate為null
        //認(rèn)證沒(méi)通過(guò),給出提示
        if(Objects.isNull(authenticate)){
            throw new RuntimeException("登陸失敗");
        }
        //認(rèn)證通過(guò),使用userid生成jwt,jwt存入ResponseResult返回
          //獲取userId
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        Map<String,String> map = new HashMap<>();
        map.put("token",jwt);
        //完整信息存入redis,userid作key
        redisCache.setCacheObject("login:"+userId,loginUser);
        return new ResponseResult(200,"登陸成功",map);
    }
}

springSecurity流程圖中是通過(guò)獲取AuthenticationManager的authenticate方法進(jìn)行認(rèn)證。通過(guò)SecurityConfig中注入的bean獲取AuthenticationManager。

authenticationManager的authenticate方法需要一個(gè)Authentication實(shí)現(xiàn)類(lèi)參數(shù),所以我們創(chuàng)建一個(gè)UsernamePasswordAuthenticationToken實(shí)現(xiàn)類(lèi)

其中的JwtUtil.createJWT(userId);方法,是我自定義的根據(jù)userId生成JWT的工具類(lèi)方法:

public class JwtUtil {
    //有效期為
    public static final Long JWT_TTL = 60*60*1000L;//一個(gè)小時(shí)
    //設(shè)置密鑰明文 。隨便定義,方便記憶和使用即可,但需要長(zhǎng)度要為4的倍數(shù)。
    public static final String JWT_KEY = "jyue";
    public static String getUUID(){
        String token = UUID.randomUUID().toString().replaceAll("-", "");
        return token;
    }
    //生成JWT
    //subject為token中存放的數(shù)據(jù)(json格式)
    public static String createJWT(String subject){
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());//設(shè)置過(guò)期時(shí)間
        return builder.compact();
    }
    public static JwtBuilder getJwtBuilder(String subject,Long ttlMillis,String uuid){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey=generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis ==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMills=nowMillis+ttlMillis;
        Date expDate = new Date(expMills);
        return Jwts.builder()
                .setId(uuid) //唯一id
                .setSubject(subject) //主題 可以是JSON數(shù)據(jù)
                .setIssuer("jy") //簽發(fā)者,隨便寫(xiě)
                .setIssuedAt(now) //簽發(fā)時(shí)間
                .signWith(signatureAlgorithm,secretKey) //使用HS256對(duì)稱(chēng)加密算法簽名,第二個(gè)參數(shù)為密鑰。
                .setExpiration(expDate);
    }
    public static SecretKey generalKey(){
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKeySpec key = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
        return key;
    }
    public static Claims parseJWT(String jwt)throws Exception{
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

redisCache也為自定義的redis工具類(lèi):

@SuppressWarnings(value = {"unchecked","rawtypes"})
@Component
public class RedisCache {
    @Autowired
    public RedisTemplate redisTemplate;
    //緩存對(duì)象
    //key 緩存鍵值
    //value 緩存值
    public <T> void setCacheObject(final String key,final T value){
        redisTemplate.opsForValue().set(key,value);
    }
    //獲取緩存的基本對(duì)象
    // key 鍵值
    // return 緩存鍵對(duì)應(yīng)的數(shù)據(jù)
    public <T>T getCacheObject(final String key){
        ValueOperations<String,T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
}

JWT認(rèn)證:

@Component            //繼承這個(gè)實(shí)現(xiàn)類(lèi),保證了請(qǐng)求只會(huì)經(jīng)過(guò)該過(guò)濾器一次
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private RedisCache redisCache;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //首先需要從請(qǐng)求頭中獲取token
        String token = request.getHeader("token");
        //判斷token是否為Null
        if(!StringUtils.hasText(token)) {
            //token沒(méi)有的話,直接放行,拋異常的活交給后續(xù)專(zhuān)門(mén)的過(guò)濾器。
            filterChain.doFilter(request, response);
            //響應(yīng)時(shí)還會(huì)經(jīng)過(guò)該過(guò)濾器一次,直接return,不能執(zhí)行下面的解析token的代碼。
            return;
        }
        //如何不為空,解析token,獲得了UserId
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            //token格式異常,不是正經(jīng)token
            throw new RuntimeException("token非法");
        }
        //根據(jù)UserId查redis獲取用戶(hù)數(shù)據(jù)
        String key = "login:"+userId;
        LoginUser loginUser = redisCache.getCacheObject(key);
        if(Objects.isNull(loginUser)){
            throw new RuntimeException("用戶(hù)未登錄");
        }
        //然后封裝Authentication對(duì)象存入SecurityContextHolder
        // 因?yàn)楹罄m(xù)的過(guò)濾器會(huì)從SecurityContextHolder中獲取信息判斷認(rèn)證情況,而決定是否放行。
        // 這里用UsernamePasswordAuthenticationToken三個(gè)參數(shù)的構(gòu)造函數(shù),是因?yàn)槠淠茉O(shè)置已認(rèn)證的狀態(tài)(因?yàn)橐呀?jīng)從redis中獲取了信息,確認(rèn)是認(rèn)證的了)
        //第一個(gè)參數(shù)為用戶(hù)信息,第三個(gè)參數(shù)為權(quán)限信息,目前還沒(méi)獲取,先填null
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser,null,null);
        //默認(rèn)SecurityContextHolder是ThreadLocal線程私有的,這也是為什么上面要用UsernamePasswordAuthenticationToken三個(gè)參數(shù)的構(gòu)造方法
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        filterChain.doFilter(request,response);
    }
}

這樣登陸后用戶(hù)發(fā)送請(qǐng)求,后端會(huì)先從請(qǐng)求頭中獲取token,然后解析出userId,然后從redis中查詢(xún)?cè)撚脩?hù)詳細(xì)信息。然后把用戶(hù)的詳細(xì)信息存入U(xiǎn)sernamePasswordAuthenticationToken三個(gè)參數(shù)的構(gòu)造函數(shù),是因?yàn)槠淠茉O(shè)置已認(rèn)證的狀態(tài)(因?yàn)橐呀?jīng)從redis中獲取了信息,確認(rèn)是認(rèn)證的了),然后把UsernamePasswordAuthenticationToken存入SecurityContextHolder。

因?yàn)楹罄m(xù)的過(guò)濾器會(huì)從SecurityContextHolder中獲取信息判斷認(rèn)證情況,而決定是否放行。

到此這篇關(guān)于springSecurity自定義登陸接口和JWT認(rèn)證過(guò)濾器的文章就介紹到這了,更多相關(guān)springSecurity自定義登陸接口內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot文件上傳簡(jiǎn)單實(shí)例代碼

    Spring Boot文件上傳簡(jiǎn)單實(shí)例代碼

    在本篇文章里小編給大家分享的是關(guān)于Spring Boot 文件上傳簡(jiǎn)易教程以及相關(guān)知識(shí)點(diǎn),需要的朋友們參考下。
    2019-08-08
  • 解決線程池中ThreadGroup的坑

    解決線程池中ThreadGroup的坑

    這篇文章主要介紹了解決線程池中ThreadGroup的坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Java實(shí)現(xiàn)拖拽文件上傳dropzone.js的簡(jiǎn)單使用示例代碼

    Java實(shí)現(xiàn)拖拽文件上傳dropzone.js的簡(jiǎn)單使用示例代碼

    本篇文章主要介紹了Java實(shí)現(xiàn)拖拽文件上傳dropzone.js的簡(jiǎn)單使用示例代碼,具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-07-07
  • 關(guān)于jpa?querydsl嵌套查詢(xún)demo

    關(guān)于jpa?querydsl嵌套查詢(xún)demo

    這篇文章主要介紹了關(guān)于jpa?querydsl?嵌套查詢(xún)demo,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Spring Boot的Maven插件Spring Boot Maven plugin詳解

    Spring Boot的Maven插件Spring Boot Maven plu

    Spring Boot的Maven插件Spring Boot Maven plugin以Maven的方式提供Spring Boot支持,Spring Boot Maven plugin將Spring Boot應(yīng)用打包為可執(zhí)行的jar或war文件,然后以通常的方式運(yùn)行Spring Boot應(yīng)用,本文介紹Spring Boot的Maven插件Spring Boot Maven plugin,一起看看吧
    2024-01-01
  • JAVA數(shù)據(jù)結(jié)構(gòu)之漢諾塔代碼實(shí)例

    JAVA數(shù)據(jù)結(jié)構(gòu)之漢諾塔代碼實(shí)例

    這篇文章主要介紹了JAVA數(shù)據(jù)結(jié)構(gòu)之漢諾塔,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Java邏輯運(yùn)算符之&&、||?與&、?|的區(qū)別及應(yīng)用

    Java邏輯運(yùn)算符之&&、||?與&、?|的區(qū)別及應(yīng)用

    這篇文章主要介紹了Java邏輯運(yùn)算符之&&、||?與&、?|的區(qū)別及應(yīng)用的相關(guān)資料,分別是&&、||?與&、?|,并探討了它們?cè)诓煌瑧?yīng)用場(chǎng)景中的表現(xiàn)和優(yōu)化效果,需要的朋友可以參考下
    2025-03-03
  • Spring Boot的listener(監(jiān)聽(tīng)器)簡(jiǎn)單使用實(shí)例詳解

    Spring Boot的listener(監(jiān)聽(tīng)器)簡(jiǎn)單使用實(shí)例詳解

    監(jiān)聽(tīng)器(Listener)的注冊(cè)方法和 Servlet 一樣,有兩種方式:代碼注冊(cè)或者注解注冊(cè)。接下來(lái)通過(guò)本文給大家介紹Spring Boot的listener(監(jiān)聽(tīng)器)簡(jiǎn)單使用,需要的朋友可以參考下
    2017-04-04
  • Java中jdk1.8和jdk17相互切換實(shí)戰(zhàn)步驟

    Java中jdk1.8和jdk17相互切換實(shí)戰(zhàn)步驟

    之前做Java項(xiàng)目時(shí)一直用的是jdk1.8,現(xiàn)在想下載另一個(gè)jdk版本17,并且在之后的使用中可以進(jìn)行相互切換,下面這篇文章主要給大家介紹了關(guān)于Java中jdk1.8和jdk17相互切換的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • 簡(jiǎn)單探索 Java 中的惰性計(jì)算

    簡(jiǎn)單探索 Java 中的惰性計(jì)算

    這篇文章主要介紹了簡(jiǎn)單探索 Java 中的惰性計(jì)算,惰性計(jì)算(盡可能延遲表達(dá)式求值)是許多函數(shù)式編程語(yǔ)言的特性。惰性集合在需要時(shí)提供其元素,無(wú)需預(yù)先計(jì)算它們,這帶來(lái)了一些好處。,需要的朋友可以參考下
    2019-06-06

最新評(píng)論