天天看點

SpringSecurity實作使用者名密碼登入(Token)

  

SpringSecurity實作使用者名密碼登入(Token)

傳統的應用是将Session放在應用伺服器上,而将生成的JSESSIONID放在使用者浏覽器的Cookie中,而這種模式在前後端分離中就會出現以下問題

    1,開發繁瑣。

    2,安全性和客戶體驗差

    3,有些前端技術不支援Cookie,如微信小程式

  這種情況下,前後端之間使用Token(令牌)進行通信就完美的解決上面的問題。

SpringSecurity實作使用者名密碼登入(Token)
⒈添加pom依賴

1         <dependency>
 2             <groupId>org.springframework.boot</groupId>
 3             <artifactId>spring-boot-starter-security</artifactId>
 4         </dependency>
 5         <dependency>
 6             <groupId>org.springframework.boot</groupId>
 7             <artifactId>spring-boot-starter-web</artifactId>
 8         </dependency>
 9         <dependency>
10             <groupId>org.springframework.security.oauth</groupId>
11             <artifactId>spring-security-oauth2</artifactId>
12             <version>2.3.5.RELEASE</version>
13         </dependency>
14         <dependency>
15             <groupId>commons-collections</groupId>
16             <artifactId>commons-collections</artifactId>
17             <version>3.2.2</version>
18         </dependency>
19         <dependency>
20             <groupId>org.springframework.boot</groupId>
21             <artifactId>spring-boot-starter-test</artifactId>
22             <scope>test</scope>
23         </dependency>
24         <dependency>
25             <groupId>org.springframework.security</groupId>
26             <artifactId>spring-security-test</artifactId>
27             <scope>test</scope>
28         </dependency>      

⒉編寫AuthenticationSuccessHandler的實作

1 package cn.coreqi.handler;
  2 
  3 import com.fasterxml.jackson.databind.ObjectMapper;
  4 import org.apache.commons.codec.binary.StringUtils;
  5 import org.apache.commons.collections.MapUtils;
  6 import org.slf4j.Logger;
  7 import org.slf4j.LoggerFactory;
  8 import org.springframework.beans.factory.annotation.Autowired;
  9 import org.springframework.security.authentication.BadCredentialsException;
 10 import org.springframework.security.core.Authentication;
 11 import org.springframework.security.oauth2.common.OAuth2AccessToken;
 12 import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
 13 import org.springframework.security.oauth2.provider.*;
 14 import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
 15 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
 16 import org.springframework.stereotype.Component;
 17 import javax.servlet.ServletException;
 18 import javax.servlet.http.HttpServletRequest;
 19 import javax.servlet.http.HttpServletResponse;
 20 import java.io.IOException;
 21 import java.util.Base64;
 22 
 23 @Component("coreqiAuthenticationSuccessHandler")
 24 public class CoreqiAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
 25 
 26     private Logger logger = LoggerFactory.getLogger(getClass());
 27 
 28     @Autowired
 29     private ClientDetailsService clientDetailsService;
 30 
 31     @Autowired
 32     private AuthorizationServerTokenServices authorizationServerTokenServices;
 33 
 34     @Autowired
 35     private ObjectMapper objectMapper;  //将對象轉換為Json的工具類,SpringMVC在啟動的時候會自動為我們注冊ObjectMapper
 36 
 37     /**
 38      * @param request    不知道
 39      * @param response   不知道
 40      * @param authentication   Authentication接口是SpringSecurity的一個核心接口,它的作用是封裝我們的認證資訊,包含認證請求中的一些資訊,包括認證請求的ip,Session是什麼,以及認證使用者的資訊等等。
 41      * @throws IOException
 42      * @throws ServletException
 43      */
 44     @Override
 45     public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
 46         //1.從請求參數中拿到ClientId
 47         String header = request.getHeader("Authorization");
 48         if (header == null && !header.toLowerCase().startsWith("basic ")) {
 49             throw new UnapprovedClientAuthenticationException("請求頭中無client資訊!");
 50         }
 51         String[] tokens = this.extractAndDecodeHeader(header, request);
 52         assert tokens.length == 2;
 53 
 54         String clientId = tokens[0];
 55         String clientSecret = tokens[1];
 56 
 57         //2.通過ClientId拿到ClientDetails
 58         ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
 59         if(clientDetails == null){
 60             throw new UnapprovedClientAuthenticationException("clientId對應的配置資訊不存在:" + clientId);
 61         }else if(!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
 62             throw new UnapprovedClientAuthenticationException("clientSecret不比對:" + clientId);
 63         }
 64         //3.建立TokenRequest
 65         TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom");
 66 
 67         //4.建構OAuth2Request
 68         OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
 69 
 70         //5.建構OAuth2Authentication
 71         OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,authentication);
 72 
 73         //6.建構OAuth2AccessToken
 74         OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
 75 
 76         //7.将生成的Token傳回給請求
 77         response.setContentType("application/json;charset=UTF-8");
 78         response.getWriter().write(objectMapper.writeValueAsString(token));
 79     }
 80 
 81     /**
 82      * 從請求頭中解析使用者名密碼
 83      * @param header
 84      * @param request
 85      * @return
 86      * @throws IOException
 87      */
 88     private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
 89         byte[] base64Token = header.substring(6).getBytes("UTF-8");
 90 
 91         byte[] decoded;
 92         try {
 93             decoded = Base64.getDecoder().decode(base64Token);
 94         } catch (IllegalArgumentException var7) {
 95             throw new BadCredentialsException("Failed to decode basic authentication token");
 96         }
 97 
 98         String token = new String(decoded, "UTF-8");
 99         int delim = token.indexOf(":");
100         if (delim == -1) {
101             throw new BadCredentialsException("Invalid basic authentication token");
102         } else {
103             return new String[]{token.substring(0, delim), token.substring(delim + 1)};
104         }
105     }
106 
107 }      

⒊配置Security

1 package cn.coreqi.config;
 2 
 3 import org.springframework.context.annotation.Bean;
 4 import org.springframework.security.authentication.AuthenticationManager;
 5 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 6 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 7 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 8 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 9 import org.springframework.security.crypto.password.NoOpPasswordEncoder;
10 import org.springframework.security.crypto.password.PasswordEncoder;
11 
12 @EnableWebSecurity
13 public class CoreqiWebSecurityConfig extends WebSecurityConfigurerAdapter {
14 
15     @Override
16     @Bean
17     public AuthenticationManager authenticationManagerBean() throws Exception {
18         return super.authenticationManagerBean();
19     }
20 
21     @Override
22     protected void configure(HttpSecurity http) throws Exception {
23         http.httpBasic()
24                 .and()
25                 .authorizeRequests()
26                 .antMatchers("/oauth/token","/login").permitAll()
27                 .anyRequest().authenticated()  //任何請求都需要身份認證
28                 .and().csrf().disable();    //禁用CSRF
29     }
30 
31     @Override
32     protected void configure(AuthenticationManagerBuilder auth) throws Exception {
33         auth.inMemoryAuthentication()
34                 .withUser("fanqi").password("admin").roles("admin");
35     }
36 
37     @Bean
38     public PasswordEncoder passwordEncoder()
39     {
40         return NoOpPasswordEncoder.getInstance();
41     }
42 }      

⒋配置OAuth2

1 package cn.coreqi.config;
 2 
 3 import org.springframework.beans.factory.annotation.Autowired;
 4 import org.springframework.beans.factory.annotation.Qualifier;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.security.authentication.AuthenticationManager;
 7 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
 8 import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
 9 import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
10 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
11 import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
12 
13 @Configuration
14 @EnableAuthorizationServer  //開啟認證伺服器
15 public class CoreqiAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
16 
17     @Autowired
18     @Qualifier("authenticationManagerBean")
19     private AuthenticationManager authenticationManager;
20 
21     @Autowired
22     private AuthenticationConfiguration authenticationConfiguration;
23 
24     /**
25      * password模式需要提供一個AuthenticationManager到AuthorizationServerEndpointsConfigurer
26      * @param authorizationServerEndpointsConfigurer
27      * @throws Exception
28      */
29     @Override
30     public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception {
31         authorizationServerEndpointsConfigurer.authenticationManager(authenticationConfiguration.getAuthenticationManager());
32     }
33 
34     @Override
35     public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
36         clientDetailsServiceConfigurer.inMemory()
37                 .withClient("coreqi")
38                 .secret("coreqiSecret")
39                 .redirectUris("https://www.baidu.com")
40                 .scopes("ALL")
41                 .authorities("COREQI_READ")
42                 .authorizedGrantTypes("authorization_code","password");
43     }
44 
45 }      

⒌配置資源伺服器

1 package cn.coreqi.config;
 2 
 3 import cn.coreqi.handler.CoreqiAuthenticationSuccessHandler;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.context.annotation.Configuration;
 6 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 7 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
 8 import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
 9 import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
10 
11 @Configuration
12 @EnableResourceServer   //開啟資源伺服器
13 public class CoreqiResourceServerConfig extends ResourceServerConfigurerAdapter {
14 
15     @Autowired
16     private CoreqiAuthenticationSuccessHandler coreqiAuthenticationSuccessHandler;
17 
18     @Override
19     public void configure(HttpSecurity http) throws Exception {
20         http.formLogin()
21                 .successHandler(coreqiAuthenticationSuccessHandler)
22             .and()
23             .authorizeRequests()
24                 .antMatchers("/oauth/token","/login").permitAll()
25                 .anyRequest().authenticated()  //任何請求都需要身份認證
26             .and()
27             .csrf()
28                 .disable();    //禁用CSRF
29     }
30 
31 }      

⒍測試

SpringSecurity實作使用者名密碼登入(Token)
SpringSecurity實作使用者名密碼登入(Token)

    

作者:奇

出處:https://www.cnblogs.com/fanqisoft/

本文版權歸作者和部落格園共有,歡迎轉載,但必須給出原文連結,并保留此段聲明,否則保留追究法律責任的權利。