SpringSecurity实现用户名密码登录(Token)
传统的应用是将Session放在应用服务器上,而将生成的JSESSIONID放在用户浏览器的Cookie中,而这种模式在前后端分离中就会出现以下问题
1,开发繁琐。
2,安全性和客户体验差
3,有些前端技术不支持Cookie,如微信小程序
这种情况下,前后端之间使用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 }
⒍测试