spring-security實現的token授權
在我的使用者密碼授權文章裡介紹了spring-security的工作過程,不瞭解的同學,可以先看看使用者密碼授權這篇文章,在
使用者密碼授權模式裡,主要是通過一個登陸頁進行授權,然後把授權物件寫到session裡,它主要用在mvc框架裡,而對於webapi來說,一般不會採用這種方式,對於webapi
來說,一般會用jwt授權方式,就是token授權碼的方式,每訪問api介面時,在http頭上帶著你的token碼,而大叔自己也寫了一個簡單的jwt授權模式,下面介紹一下。
WebSecurityConfig授權配置
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter { /** * token過濾器. */ @Autowired LindTokenAuthenticationFilter lindTokenAuthenticationFilter; @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .csrf().disable() // 基於token,所以不需要session .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() // 對於獲取token的rest api要允許匿名訪問 .antMatchers("/lind-auth/**").permitAll() // 除上面外的所有請求全部需要鑑權認證 .anyRequest().authenticated(); httpSecurity .addFilterBefore(lindTokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 禁用快取 httpSecurity.headers().cacheControl(); } /** * 密碼生成策略. * * @return */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
授權介面login
對外開放的,需要提供使用者名稱和密碼為引數進行登陸,然後返回token碼,當然也可以使用手機號和驗證碼登陸,授權邏輯是一樣的,獲取使用者資訊都是使用UserDetailsService
,
然後開發人員根據自己的業務去重寫loadUserByUsername
來獲取使用者實體。
使用者登陸成功後,為它授權及認證,這一步我們會在redis裡建立token與使用者名稱的關係。
@GetMapping(LOGIN) public ResponseEntity<?> refreshAndGetAuthenticationToken( @RequestParam String username, @RequestParam String password) throws AuthenticationException { return ResponseEntity.ok(generateToken(username, password)); } /** * 登陸與授權. * * @param username . * @param password . * @return */ private String generateToken(String username, String password) { UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password); // Perform the security final Authentication authentication = authenticationManager.authenticate(upToken); SecurityContextHolder.getContext().setAuthentication(authentication); // Reload password post-security so we can generate token final UserDetails userDetails = userDetailsService.loadUserByUsername(username); // 持久化的redis String token = CommonUtils.encrypt(userDetails.getUsername()); redisTemplate.opsForValue().set(token, userDetails.getUsername()); return token; }
LindTokenAuthenticationFilter程式碼
主要實現了對請求的攔截,獲取http頭上的Authorization
元素,token碼就在這個鍵裡,我們的token都是採用通用的Bearer
開頭,當你的token沒有過期時,會
儲存在redis裡,key就是使用者名稱的md5碼,而value就是使用者名稱,當拿到token之後去資料庫或者快取裡拿使用者資訊進行授權即可。
/** * token filter bean. */ @Component public class LindTokenAuthenticationFilter extends OncePerRequestFilter { @Autowired RedisTemplate<String, String> redisTemplate; String tokenHead = "Bearer "; String tokenHeader = "Authorization"; @Autowired private UserDetailsService userDetailsService; /** * token filter. * * @param request. * @param response. * @param filterChain . */ @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader(this.tokenHeader); if (authHeader != null && authHeader.startsWith(tokenHead)) { final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer " if (authToken != null && redisTemplate.hasKey(authToken)) { String username = redisTemplate.opsForValue().get(authToken); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); //可以校驗token和username是否有效,目前由於token對應username存在redis,都以預設都是有效的 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( request)); logger.info("authenticated user " + username + ", setting security context"); SecurityContextHolder.getContext().setAuthentication(authentication); } } } filterChain.doFilter(request, response); }
測試token授權
get:http://localhost:8080/lind-demo/login?username=admin&password=123 post:http://localhost:8080/lind-demo/user/add Content-Type:application/json Authorization:Bearer 21232F297A57A5A743894A0E4A801FC3