重构用户名密码登录
让自己的逻辑获取token的话,oath前面的逻辑都不能使用。使用我们自己的逻辑来代替。
也就是相当于只使用后面的功能;把自定义认证模式添加进来
在AuthenticationSuccessHandle中存在authentication对象,
所以只要获取到 ClientDetails和TokenRequest即可; 有时间了查看源码找这些吧
思路
-
提交登录请求
-
登录成功之后,需要在上图AuthenticationSuccessHandler中获取相关信息
- 前提条件:必须携带basic client信息(因为需要它获取clientDetails信息)
-
然后走后面的逻辑
处理登录后的逻辑
package cn.mrcode.imooc.springsecurity.securitycore.authentication;
import cn.mrcode.imooc.springsecurity.securitycore.properties.LoginType;
import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
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.config.annotation.configuration.ClientDetailsServiceConfiguration;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestFactory;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
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;
/**
* 复制之前写好的handler进行修改,只支持app这种模式
* @author zhuqiang
* @version 1.0.1 2018/8/3 16:29
* @date 2018/8/3 16:29
* @since 1.0
*/
@Component("myAuthenticationSuccessHandler")
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
private org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
// com.fasterxml.jackson.databind.
// spring 是使用jackson来进行处理返回数据的
// 所以这里可以得到他的实例
@Autowired
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
@Autowired
private SecurityProperties securityProperties;
/**
* 授权服务器:自动配置的
* @see ClientDetailsServiceConfiguration#clientDetailsService()
*/
@Autowired
private ClientDetailsService clientDetailsService;
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
/**
* @param request
* @param response
* @param authentication 封装了所有的认证信息
* @throws IOException
* @throws ServletException
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
logger.info("登录成功");
/**
* @see BasicAuthenticationFilter#doFilterInternal(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, javax.servlet.FilterChain)
* */
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Basic ")) {
// 不被认可的客户端异常
throw new UnapprovedClientAuthenticationException("没有Authorization请求头");
}
// 解析请Authorization 获取client信息
// client-id: myid
// client-secret: myid
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String clientId = tokens[0];
String clientSecret = tokens[1];
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);
}
/** @see DefaultOAuth2RequestFactory#createTokenRequest(java.util.Map, org.springframework.security.oauth2.provider.ClientDetails)
* requestParameters,不同的授权模式有不同的参数,这里自定义的模式,没有参数
* String clientId,
* Collection<String> scope, 给自己的前段使用,默认用所有的即可
* String grantType 自定义
*
* 在这里我就有一个疑问了:这个token应该代表的是不同的用户,这里使用我们配置的同一个client?那么获取到的不就是相同的token?
* 难道说是根据用户名和密码创建的?以后明白了再来填坑
* */
TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_SORTED_MAP, clientId, clientDetails.getScope(), "costom");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
/**
* @see org.springframework.security.oauth2.provider.token.AbstractTokenGranter#getOAuth2Authentication(org.springframework.security.oauth2.provider.ClientDetails, org.springframework.security.oauth2.provider.TokenRequest)
* */
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
// 在后面测试的时候居然抛出了一个 事物异常 Could not open JDBC Connection for transaction; nested exception is ja
// 我的数据库密码写错了,这个方法上加了一个@Transactional注解
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(accessToken));
}
/**
* Decodes the header into a username and password.
* @throws BadCredentialsException if the Basic header is not present or is not valid
* Base64
*/
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 e) {
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");
}
return new String[]{token.substring(0, delim), token.substring(delim + 1)};
}
}
资源服务器安全配置
其实资源服务器的安全配置就类似普通服务的security的配置,对资源的保护;
spring oath2是建立的security的逻辑上的;不做用户认证,只做授权服务器,发送令牌,验证令牌等功能
所以按照之前配置过的copy过来快速修改,跑起来再细化修改
package cn.mrcode.imooc.springsecurity.securityapp;
/*
*
* ${desc}
* @author zhuqiang
* @version 1.0.1 2018/8/7 13:15
* @date 2018/8/7 13:15
* @since 1.0
* */
import cn.mrcode.imooc.springsecurity.securitycore.authentication.mobile.SmsCodeAuthenticationSecurityConfig;
import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityConstants;
import cn.mrcode.imooc.springsecurity.securitycore.properties.SecurityProperties;
import cn.mrcode.imooc.springsecurity.securitycore.validate.code.ValidateCodeSecurityConfig;
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.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.social.security.SpringSocialConfigurer;
@Configuration
@EnableResourceServer
public class MyResourcesServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
// 由下面的 .apply(smsCodeAuthenticationSecurityConfigs)方法添加这个配置
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfigs;
@Autowired
private ValidateCodeSecurityConfig validateCodeSecurityConfig;
/**
* @see SocialConfig#imoocSocialSecurityConfig()
*/
@Autowired
private SpringSocialConfigurer imoocSocialSecurityConfig;
@Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler myAuthenticationFailureHandler;
// 有三个configure的方法,这里使用http参数的
@Override
public void configure(HttpSecurity http) throws Exception {
// 之前配置的security的basic的 不能去掉哦。否则授权码模式又不能使用了
http.formLogin()
.loginPage(SecurityConstants.DEFAULT_UNAUTHENTICATION_URL)
.loginProcessingUrl(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_FORM)
.successHandler(myAuthenticationSuccessHandler)
.failureHandler(myAuthenticationFailureHandler)
;
http
// 视频中说验证码的功能还有一点问题,先不用
// .apply(validateCodeSecurityConfig)
// .and()
.apply(smsCodeAuthenticationSecurityConfigs)
.and()
.apply(imoocSocialSecurityConfig)
.and()
// 对请求授权配置:注意方法名的含义,能联想到一些
.authorizeRequests()
// 放行这个路径
.antMatchers(
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
securityProperties.getBrowser().getLoginPage(),
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX + "/*", // 图形验证码接口
securityProperties.getBrowser().getSignUpUrl(), // 注册页面
"/user/regist",
"/error",
"/connect/*",
"/auth/*",
"/signin"
)
.permitAll()
.anyRequest()
// 对任意请求都必须是已认证才能访问
.authenticated()
.and()
.csrf()
.disable()
;
}
}
验证流程是否ok
Authorization 头还是写之前的myid的client信息;访问之前蒂尼的表单登录地址
POST /authentication/form HTTP/1.1
Host: localhost:8080
Authorization: Basic bXlpZDpteWlk
Content-Type: application/x-www-form-urlencoded
Cache-Control: no-cache
Postman-Token: 932d7563-34c1-002c-e2c1-8923757877c0
username=admin&password=123456
成功获取到token信息
{
"access_token": "ab9bba42-f954-44d4-8e40-e4d6d81bfe60",
"token_type": "bearer",
"refresh_token": "e54ad634-127d-456b-9bf1-3b40c9d43017",
"expires_in": 43199
}