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

SpringSecurity?認證實現(xiàn)流程分析

 更新時間:2024年10月12日 14:51:13   作者:每天進步一......  
SpringSecurity框架通過一個過濾器鏈來處理認證和授權,主要包括UsernamePasswordAuthenticationFilter負責處理登錄請求,本文給大家介紹SpringSecurity?認證實現(xiàn)登錄校驗,感興趣的朋友跟隨小編一起看看吧

一、初步理解

SpringSecurity的原理其實就是一個過濾器鏈,內(nèi)部包含了提供各種功能的過濾器。

當前系統(tǒng)中SpringSecurity過濾器鏈中有哪些過濾器及它們的順序。

核心過濾器:

  • (認證)UsernamePasswordAuthenticationFilter:負責處理我們在登陸頁面填寫了用戶名密碼后的登陸請求
  • ExceptionTranslationFilter:處理過濾器鏈中拋出的任何AccessDeniedException和 AuthenticationException 
  • (授權)FilterSecurityInterceptor:負責權限校驗的過濾器

二、Token(Jwt)登錄校驗流程

三、具體認證授權細節(jié)

下圖是UsernamePasswordAuthenticationFilter處理用戶名、密碼,然后將用戶名、密碼、權限信息封裝到Authentication對象中,再放到SecurityContextHolder中。

Authentication接口: 它的實現(xiàn)類,表示當前訪問系統(tǒng)的用戶,封裝了用戶相關信息。

AuthenticationManager接口:定義了認證Authentication的方法

UserDetailsService接口:加載用戶特定數(shù)據(jù)的核心接口。里面定義了一個根據(jù)用戶名查詢用戶信息的 方法。

UserDetails接口:提供核心用戶信息。通過UserDetailsService根據(jù)用戶名獲取處理的用戶信息要封裝 成UserDetails對象返回。然后將這些信息封裝到Authentication對象中。

認證

  • 當用戶登錄時,前端將用戶輸入的用戶名、密碼信息傳輸?shù)胶笈_,后臺用一個類對象將其封裝起來,通常使用的是UsernamePasswordAuthenticationToken這個類。
  • 程序負責驗證這個類對象。驗證方法是調(diào)用Service根據(jù)username從數(shù)據(jù)庫中取用戶信息到實體類的實例中,比較兩者的密碼,如果密碼正確就成功登陸,同時把包含著用戶的用戶名、密碼、所具有的權限等信息(用戶id、昵稱、是否管理員)的類對象放到SecurityContextHolder(安全上下文容器,類似Session)中去。
  • 用戶訪問一個資源的時候,首先判斷是否是受限資源。如果是的話還要判斷當前是否未登錄,沒有的話就跳到登錄頁面。
  • 如果用戶已經(jīng)登錄,訪問一個受限資源的時候,程序要根據(jù)url去數(shù)據(jù)庫中取出該資源所對應的所有可以訪問的角色,然后拿著當前用戶的所有角色一一對比,判斷用戶是否可以訪問(這里就是和權限相關)。

授權

  • 在SpringSecurity中,會使用默認的FilterSecurityInterceptor來進行權限校驗。在FilterSecurityInterceptor中會從SecurityContextHolder獲取其中的Authentication,然后獲取其中的權限信息。當前用戶是否擁有訪問當前資源所需的權限。
  • 所以我們在項目中只需要把當前登錄用戶的權限信息也存入Authentication。然后設置我們的資源所需要的權限即可。

自定義登錄認證接口:①調(diào)用ProviderManager的方法進行認證;②如果認證通過生成jwt;③把用戶信息存入redis中

自定義權限信息查詢:在UserDetailsService這個實現(xiàn)類中去查詢數(shù)據(jù)庫

四、自定義權限查詢

修改UsernamePasswordAuthenticationFilter上圖最右邊的授權部分。

 1.自定義登陸接口

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    @PostMapping("/login")
    public R login(@RequestBody User user) {
        String jwt = userService.login(user);
        if (StringUtils.hasLength(jwt)) {
            return R.ok().message("登陸成功").data("token", jwt);
        }
        return R.error().message("登陸失敗");
    }
}

 2.配置數(shù)據(jù)庫校驗登錄用戶

從之前的分析我們可以知道,我們可以自定義一個UserDetailsService,讓SpringSecurity使用我們的 UserDetailsService。我們自己的UserDetailsService可以從數(shù)據(jù)庫中查詢用戶名和密碼。

創(chuàng)建一個類實現(xiàn)UserDetailsService接口,重寫loadUserByUsername方法

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
     @Autowired
     private UserMapper userMapper;
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         //查詢用戶信息
         QueryWrapper<User> queryWrapper=new QueryWrapper<>();
         queryWrapper.eq("user_name",username);
         User user = userMapper.selectOne(queryWrapper);
         //如果沒有查詢到用戶,就拋出異常
         if(Objects.isNull(user)){
             throw  new RuntimeException("用戶名或密碼錯誤");
         }
         //TODO 查詢用戶對應的權限信息
         細節(jié)見SpringSecurity(二)——授權實現(xiàn)
         //如果有,把數(shù)據(jù)封裝成UserDetails對象返回
         return new LoginUser(user);
    }
}

五、Jwt認證過濾器(自定義過濾器)

(1)在接口中我們通過AuthenticationManager的authenticate方法來進行用戶認證,所以需要在 SecurityConfig中配置把AuthenticationManager注入容器。

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig{
    /**
     * 登錄時需要調(diào)用AuthenticationManager.authenticate執(zhí)行一次校驗
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

 (2)登錄的業(yè)務邏輯層實現(xiàn)類

第一次登錄,生成jwt存入redis

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    public String login(User user) {
        //1.封裝Authentication對象 ,密碼校驗,自動完成
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
        //2.進行校驗
        Authentication authenticate = authenticationManager.authenticate(authentication);
        //3.如果authenticate為空
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("登錄失敗"); //TODO 登錄失敗
        }
        //4.得到用戶信息
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        //生成jwt,使用fastjson的方法,把對象轉(zhuǎn)成字符串
        String loginUserString = JSON.toJSONString(loginUser);
        //調(diào)用JWT工具類,生成jwt令牌
        String jwt = JwtUtils.createJWT(loginUserString, null);
        //5.把生成的jwt存到redis
        String tokenKey = "token_" + jwt;
        stringRedisTemplate.opsForValue().set(tokenKey, jwt, JwtUtils.JWT_TTL / 1000);
        Map<String, Object> map = new HashMap<>();
        map.put("token", jwt);
        map.put("username", loginUser.getUsername());
        return jwt;
    }
}

(3)jwt認證校驗過濾器

我們需要自定義一個過濾器,這個過濾器會去獲取請求頭中的token,對token進行解析取出其中的 userid。 使用userid去redis中獲取對應的LoginUser對象。

然后封裝Authentication對象存入SecurityContextHolder

/**
 * token驗證過濾器   //每一個servlet請求,只會執(zhí)行一次
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private LoginFailureHandler loginFailureHandler;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        try {
            //1.獲取當前請求的url地址
            String url = request.getRequestURI();
            //如果當前請求不是登錄請求,則需要進行token驗證
            if (!url.equals("/user/login")) {
                //2.驗證token
                this.validateToken(request);
            }
        } catch (AuthenticationException e) {
            System.out.println(e);
            loginFailureHandler.onAuthenticationFailure(request, response, e);
        }
        //3.登錄請求不需要驗證token
        doFilter(request, response, filterChain);
    }
    /**
     * 驗證token
     */
    private void validateToken(HttpServletRequest request) throws AuthenticationException {
        //1.獲取token
        String token = request.getHeader("Authorization");
        //如果請求頭部沒有獲取到token,則從請求的參數(shù)中進行獲取
        if (ObjectUtils.isEmpty(token)) {
            token = request.getParameter("Authorization");
        }
        if (ObjectUtils.isEmpty(token)) {
            throw new CustomerAuthenticationException("token不存在");
        }
        //2.redis進行校驗
        String redisStr = stringRedisTemplate.opsForValue().get("token_" + token);
        if(ObjectUtils.isEmpty(redisStr)) {
            throw new CustomerAuthenticationException("token已過期");
        }
        //3.解析token
        Claims claims = null;
        try {
            claims = JwtUtils.parseJWT(token);
        } catch (Exception e) {
            throw new CustomerAuthenticationException("token解析失敗");
        }
        //4.獲取到用戶信息
        String loginUserString = claims.getSubject();
        //把字符串轉(zhuǎn)成loginUser對象
        LoginUser loginUser = JSON.parseObject(loginUserString, LoginUser.class);
        //創(chuàng)建身份驗證對象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        //5.設置到Spring Security上下文
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }
}

(4)把jwt過濾器注冊到springsecurity過濾器鏈中

放在UsernamePasswordAuthenticationFilter前面

@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig{
    //自定義jwt校驗過濾器
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //配置關閉csrf機制
        http.csrf(csrf -> csrf.disable());
        //登陸失敗處理器
        http.formLogin(configurer -> {
            configurer.failureHandler(loginFailureHandler);
        });
        http.sessionManagement(configurer ->
                // STATELESS(無狀態(tài)): 表示應用程序是無狀態(tài)的,不會創(chuàng)建會話。
                configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        );
        //請求攔截方式
        http.authorizeHttpRequests(auth -> auth
                .requestMatchers("/user/login").permitAll()
                .anyRequest().authenticated()
        );
        //!?。。?!注冊jwt過濾器?。。。。。。?
        http.addFilterBefore(jwtAuthenticationTokenFilter,      
                             UsernamePasswordAuthenticationFilter.class);
        //異常處理器
        http.exceptionHandling(configurer -> {
            configurer.accessDeniedHandler(customerAccessDeniedHandler);
            configurer.authenticationEntryPoint(anonymousAuthenticationHandler);
        });
        return http.build();   //允許跨域
    }
    /**
     * 登錄時需要調(diào)用AuthenticationManager.authenticate執(zhí)行一次校驗
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}

到此這篇關于SpringSecurity 認證實現(xiàn)的文章就介紹到這了,更多相關SpringSecurity 認證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • JVM的類加載過程以及雙親委派模型詳解

    JVM的類加載過程以及雙親委派模型詳解

    這篇文章主要介紹了JVM的類加載過程以及雙親委派模型詳解,類加載器就是根據(jù)指定全限定名稱將 class 文件加載到 JVM 內(nèi)存,然后再轉(zhuǎn)化為 class 對象。,需要的朋友可以參考下
    2019-06-06
  • Java靈活使用枚舉表示一組字符串的操作

    Java靈活使用枚舉表示一組字符串的操作

    這篇文章主要介紹了Java靈活使用枚舉表示一組字符串的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • java Socket UDP實例詳解

    java Socket UDP實例詳解

    這篇文章主要介紹了java Socket UDP實例詳解的相關資料,需要的朋友可以參考下
    2017-02-02
  • Spring?Feign超時設置深入了解

    Spring?Feign超時設置深入了解

    Spring?Cloud中Feign客戶端是默認開啟支持Ribbon的,最重要的兩個超時就是連接超時ConnectTimeout和讀超時ReadTimeout,在默認情況下,也就是沒有任何配置下,F(xiàn)eign的超時時間會被Ribbon覆蓋,兩個超時時間都是1秒
    2023-03-03
  • Java基礎之自動裝箱,注解操作示例

    Java基礎之自動裝箱,注解操作示例

    這篇文章主要介紹了Java基礎之自動裝箱,注解操作,結(jié)合實例形式分析了java拆箱、裝箱、靜態(tài)導入、注釋等相關使用技巧,需要的朋友可以參考下
    2019-08-08
  • MybatisPlus使用排序查詢時將null值放到最后

    MybatisPlus使用排序查詢時將null值放到最后

    按照更新時間排序,但是更新時間可能為null,因此將null的數(shù)據(jù)放到最后,本文主要介紹了MybatisPlus使用排序查詢時將null值放到最后,具有一定的參考價值,感興趣的可以了解一下
    2023-08-08
  • Mybatis中where標簽與if標簽結(jié)合使用詳細說明

    Mybatis中where標簽與if標簽結(jié)合使用詳細說明

    mybatis中if和where用于動態(tài)sql的條件拼接,在查詢語句中如果缺失某個條件,通過if和where標簽可以動態(tài)的改變查詢條件,下面這篇文章主要給大家介紹了關于Mybatis中where標簽與if標簽結(jié)合使用的詳細說明,需要的朋友可以參考下
    2023-03-03
  • 教你用IDEA配置JUnit并進行單元測試

    教你用IDEA配置JUnit并進行單元測試

    今天教各位小伙伴怎么用IDEA配置JUnit并進行單元測試,文中有非常詳細的圖文介紹及代碼示例,對正在學習IDEA的小伙伴有很好的幫助,需要的朋友可以參考下
    2021-05-05
  • 使用Java將字節(jié)數(shù)組轉(zhuǎn)成16進制形式的代碼實現(xiàn)

    使用Java將字節(jié)數(shù)組轉(zhuǎn)成16進制形式的代碼實現(xiàn)

    在很多場景下,需要進行分析字節(jié)數(shù)據(jù),但是我們存起來的字節(jié)數(shù)據(jù)一般都是二進制的,這時候就需要我們將其轉(zhuǎn)成16進制的方式方便分析,本文主要介紹如何使用Java將字節(jié)數(shù)組格式化成16進制的格式并輸出,需要的朋友可以參考下
    2024-05-05
  • SpringBoot項目啟動健康檢查的操作方法

    SpringBoot項目啟動健康檢查的操作方法

    在現(xiàn)代的微服務架構中,容器化技術已經(jīng)成為一種主流的部署方式,Docker 作為容器化技術的代表,提供了一種輕量級、可移植的解決方案,然而,僅僅將應用容器化是不夠的,我們還需要確保這些容器在運行時能夠保持健康狀態(tài),這就是健康檢查發(fā)揮作用的地方
    2024-12-12

最新評論