傳統的應用是將Session放在應用服務器上,而將生成的JSESSIONID放在用戶瀏覽器的Cookie中,而這種模式在先後端分離中就會出現如下問題前端
1,開發繁瑣。java
2,安全性和客戶體驗差web
3,有些前端技術不支持Cookie,如微信小程序spring
這種狀況下,先後端之間使用Token(令牌)進行通訊就完美的解決上面的問題。apache
⒈添加pom依賴json
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 }
⒍測試