SpringSecurity实现用户名密码登录(Token)

时间:2023-03-08 16:54:07

  SpringSecurity实现用户名密码登录(Token)

传统的应用是将Session放在应用服务器上,而将生成的JSESSIONID放在用户浏览器的Cookie中,而这种模式在前后端分离中就会出现以下问题

    1,开发繁琐。

    2,安全性和客户体验差

    3,有些前端技术不支持Cookie,如微信小程序

  这种情况下,前后端之间使用Token(令牌)进行通信就完美的解决上面的问题。

SpringSecurity实现用户名密码登录(Token)

⒈添加pom依赖

         <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

⒉编写AuthenticationSuccessHandler的实现

 package cn.coreqi.handler;

 import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.collections.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Base64; @Component("coreqiAuthenticationSuccessHandler")
public class CoreqiAuthenticationSuccessHandler implements AuthenticationSuccessHandler { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired
private ClientDetailsService clientDetailsService; @Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices; @Autowired
private ObjectMapper objectMapper; //将对象转换为Json的工具类,SpringMVC在启动的时候会自动为我们注册ObjectMapper /**
* @param request 不知道
* @param response 不知道
* @param authentication Authentication接口是SpringSecurity的一个核心接口,它的作用是封装我们的认证信息,包含认证请求中的一些信息,包括认证请求的ip,Session是什么,以及认证用户的信息等等。
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//1.从请求参数中拿到ClientId
String header = request.getHeader("Authorization");
if (header == null && !header.toLowerCase().startsWith("basic ")) {
throw new UnapprovedClientAuthenticationException("请求头中无client信息!");
}
String[] tokens = this.extractAndDecodeHeader(header, request);
assert tokens.length == 2; String clientId = tokens[0];
String clientSecret = tokens[1]; //2.通过ClientId拿到ClientDetails
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
if(clientDetails == null){
throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
}else if(!StringUtils.equals(clientDetails.getClientSecret(),clientSecret)){
throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
}
//3.创建TokenRequest
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP,clientId,clientDetails.getScope(),"custom"); //4.构建OAuth2Request
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails); //5.构建OAuth2Authentication
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request,authentication); //6.构建OAuth2AccessToken
OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication); //7.将生成的Token返回给请求
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(token));
} /**
* 从请求头中解析用户名密码
* @param header
* @param request
* @return
* @throws IOException
*/
private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {
byte[] base64Token = header.substring(6).getBytes("UTF-8"); byte[] decoded;
try {
decoded = Base64.getDecoder().decode(base64Token);
} catch (IllegalArgumentException var7) {
throw new BadCredentialsException("Failed to decode basic authentication token");
} String token = new String(decoded, "UTF-8");
int delim = token.indexOf(":");
if (delim == -1) {
throw new BadCredentialsException("Invalid basic authentication token");
} else {
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
} }

⒊配置Security

 package cn.coreqi.config;

 import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; @EnableWebSecurity
public class CoreqiWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers("/oauth/token","/login").permitAll()
.anyRequest().authenticated() //任何请求都需要身份认证
.and().csrf().disable(); //禁用CSRF
} @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("fanqi").password("admin").roles("admin");
} @Bean
public PasswordEncoder passwordEncoder()
{
return NoOpPasswordEncoder.getInstance();
}
}

⒋配置OAuth2

 package cn.coreqi.config;

 import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer; @Configuration
@EnableAuthorizationServer //开启认证服务器
public class CoreqiAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager; @Autowired
private AuthenticationConfiguration authenticationConfiguration; /**
* password模式需要提供一个AuthenticationManager到AuthorizationServerEndpointsConfigurer
* @param authorizationServerEndpointsConfigurer
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception {
authorizationServerEndpointsConfigurer.authenticationManager(authenticationConfiguration.getAuthenticationManager());
} @Override
public void configure(ClientDetailsServiceConfigurer clientDetailsServiceConfigurer) throws Exception {
clientDetailsServiceConfigurer.inMemory()
.withClient("coreqi")
.secret("coreqiSecret")
.redirectUris("https://www.baidu.com")
.scopes("ALL")
.authorities("COREQI_READ")
.authorizedGrantTypes("authorization_code","password");
} }

⒌配置资源服务器

 package cn.coreqi.config;

 import cn.coreqi.handler.CoreqiAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer; @Configuration
@EnableResourceServer //开启资源服务器
public class CoreqiResourceServerConfig extends ResourceServerConfigurerAdapter { @Autowired
private CoreqiAuthenticationSuccessHandler coreqiAuthenticationSuccessHandler; @Override
public void configure(HttpSecurity http) throws Exception {
http.formLogin()
.successHandler(coreqiAuthenticationSuccessHandler)
.and()
.authorizeRequests()
.antMatchers("/oauth/token","/login").permitAll()
.anyRequest().authenticated() //任何请求都需要身份认证
.and()
.csrf()
.disable(); //禁用CSRF
} }

⒍测试

SpringSecurity实现用户名密码登录(Token)

SpringSecurity实现用户名密码登录(Token)