spring security 使用篇 使用spring soc
2018-10-20 本文已影响345人
怪诞140819
1.OAuth2协议之授权码
OAuth2协议.png对接第三方来进行认证授权主要采用的是OAuth2的授权码模式,所以我们需要对此来进行对接。OAuth2的主要流程分为以下6个步骤
- (1).将用户导向认证服务器
- (2).用户同意授权
- (3).返回client并且携带授权码
- (4).申请令牌
- (5).发放令牌
-
(6).获取用户信息
具体对于spring social对接就如下图
spring social对接流程
spring social封装了整个上篇OAuth2对接的流程,上图就是逻辑上的流程图
Spring social的接口和类
- (1). 服务提供商的抽象 ServiceProvider,Spring 有抽象类完成共有特性
AbstractOAuth2ServiceProvider
- (2).OAuth2Operations接口,默认实现OAuth2Template,主要封装了流程的每个步骤的操作
- (3).Api,spring social定义的抽象类为AbstractOAuth2ApiBinding
2.QQ登录实现
QQ登录需要实现以下效果
点击QQ登录
点击QQ登录跳转到如下
QQ登录授权页面
最后获取到用户的信息
获取QQ用户信息
2.1 准备
需要到QQ互联https://connect.qq.com/index.html申请到APP ID
和APP Key
2.2 编写Api
接口
public interface QQ {
/**
* 获取QQ用户信息
* @return
*/
QQUserInfo getUserInfo();
}
实现
/**
* @Auther: guaidan
* @Date: 2018/10/16 11:20
* @Description:
*/
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
private Logger logger = LoggerFactory.getLogger(getClass());
private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
//后面默认会加上access_token
private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
private ObjectMapper objectMapper = new ObjectMapper();
private String appId;
private String openId;
public QQImpl(String accessToken,String appId) {
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId = appId;
//这里需要获取到openId
String url = String.format(URL_GET_OPENID,accessToken);
String result = getRestTemplate().getForObject(url,String.class);
this.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
logger.info("openId is:"+this.openId);
}
@Override
public QQUserInfo getUserInfo() {
String url = String.format(URL_GET_USERINFO,this.appId,this.openId);
String result = getRestTemplate().getForObject(url,String.class);
logger.info("getQQUserInfo result is "+result);
QQUserInfo userInfo = null;
try {
userInfo = objectMapper.readValue(result, QQUserInfo.class);
return userInfo;
} catch (Exception e) {
throw new RuntimeException("获取用户信息失败", e);
}
}
}
QQ 用户信息的Bean,这里只写出字段,get和set方法生成一下
public class QQUserInfo {
/**
* 返回码
*/
private String ret;
/**
* 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。
*/
private String msg;
/**
*
*/
private String openId;
/**
* 不知道什么东西,文档上没写,但是实际api返回里有。
*/
private String is_lost;
/**
* 省(直辖市)
*/
private String province;
/**
* 市(直辖市区)
*/
private String city;
/**
* 出生年月
*/
private String year;
/**
* 用户在QQ空间的昵称。
*/
private String nickname;
/**
* 大小为30×30像素的QQ空间头像URL。
*/
private String figureurl;
/**
* 大小为50×50像素的QQ空间头像URL。
*/
private String figureurl_1;
/**
* 大小为100×100像素的QQ空间头像URL。
*/
private String figureurl_2;
/**
* 大小为40×40像素的QQ头像URL。
*/
private String figureurl_qq_1;
/**
* 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100×100的头像,但40×40像素则是一定会有。
*/
private String figureurl_qq_2;
/**
* 性别。 如果获取不到则默认返回”男”
*/
private String gender;
/**
* 标识用户是否为黄钻用户(0:不是;1:是)。
*/
private String is_yellow_vip;
/**
* 标识用户是否为黄钻用户(0:不是;1:是)
*/
private String vip;
/**
* 黄钻等级
*/
private String yellow_vip_level;
/**
* 黄钻等级
*/
private String level;
/**
* 标识是否为年费黄钻用户(0:不是; 1:是)
*/
private String is_yellow_year_vip;
/**
* 不知道什么玩意儿
*/
private String constellation;
}
2.3 自定义OAuth2操作的Template
public class QQOAuth2Template extends OAuth2Template {
private Logger logger = LoggerFactory.getLogger(getClass());
public QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
//设置系统自动填充clientId和clientSecret等参数
setUseParametersForClientAuthentication(true);
}
@Override
protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap<String, String> parameters) {
String responseText = getRestTemplate().postForObject(accessTokenUrl,parameters,String.class);
logger.info("response access token text:"+responseText);
String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseText,"&");
String accessToken = StringUtils.substringAfterLast(items[0], "=");
Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
String refreshToken = StringUtils.substringAfterLast(items[2], "=");
return new AccessGrant(accessToken,null,refreshToken,expiresIn);
}
@Override
protected RestTemplate createRestTemplate(){
RestTemplate restTemplate = super.createRestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
}
2.4 自定义Provider
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
private String appId;
private String appSecret;
private static final String AUTHORIZE_URL = "https://graph.qq.com/oauth2.0/authorize";
private static final String ACCESS_TOKEN_URL = "https://graph.qq.com/oauth2.0/token";
public QQServiceProvider(String appId,String appSecret) {
super(new QQOAuth2Template(appId, appSecret, AUTHORIZE_URL, ACCESS_TOKEN_URL));
this.appId = appId;
this.appSecret = appSecret;
}
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken,appId);
}
2.5 自定义ApiAdaptor
public class QQAdaptor implements ApiAdapter<QQ> {
@Override
public boolean test(QQ qq) {
//这里表示测试QQ服务是否正产,返回true,表示相信QQ的服务是永远正常的
return true;
}
@Override
public void setConnectionValues(QQ api, ConnectionValues values) {
QQUserInfo userInfo = api.getUserInfo();
values.setDisplayName(userInfo.getNickname());
values.setImageUrl(userInfo.getFigureurl_qq_1());
values.setProfileUrl(null);//这里表示主页地址
values.setProviderUserId(userInfo.getOpenId());
}
@Override
public UserProfile fetchUserProfile(QQ qq) {
// do nothing
return null;
}
@Override
public void updateStatus(QQ qq, String s) {
// do nothing
}
}
2.6 自定义ConnectionFactory
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
public QQConnectionFactory(String providerId,String appId,String appSecret) {
super(providerId, new QQServiceProvider(appId,appSecret), new QQAdaptor());
}
}
2.7 自定义SpringSocialConfigurer
public class CustomSpringSocialConfigurer extends SpringSocialConfigurer {
private String filterProcessesUrl;
public CustomSpringSocialConfigurer(String filterProcessesUrl){
this.filterProcessesUrl = filterProcessesUrl;
}
@Override
protected <T> T postProcess(T object) {
SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);
filter.setFilterProcessesUrl(filterProcessesUrl);
return (T) filter;
}
}
2.8自定义SocialConfigurer
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
private SecurityProperties securityProperties;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
//这里需要数据库创建表,建表语句,请查看 JdbcUsersConnectionRepository.sql
//第一个参数数据源,第二个参数加载不同的ConnectionFactory,第三个参数加密
JdbcUsersConnectionRepository jdbcUsersConnectionRepository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
return jdbcUsersConnectionRepository;
}
@Bean(name = "customSocialConfigure")
public SpringSocialConfigurer customSocialConfigure(){
CustomSpringSocialConfigurer customSpringSocialConfigurer = new CustomSpringSocialConfigurer(securityProperties.getSocial().getFilterProcessesUrl());
return customSpringSocialConfigurer;
}
}
2.9 相关自定义配置
@ConfigurationProperties(prefix = "guaidan.security")
public class SecurityProperties {
private SocialProperties social = new SocialProperties();
public SocialProperties getSocial() {
return social;
}
public void setSocial(SocialProperties social) {
this.social = social;
}
}
public class SocialProperties {
private String filterProcessesUrl = "/auth";
private QQProperties qq = new QQProperties();
public String getFilterProcessesUrl() {
return filterProcessesUrl;
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.filterProcessesUrl = filterProcessesUrl;
}
public QQProperties getQq() {
return qq;
}
public void setQq(QQProperties qq) {
this.qq = qq;
}
}
import org.springframework.boot.autoconfigure.social.SocialProperties;
/**
* 这里的SocialProperties是系统的不是自己定义的
* @Auther: guaidan
* @Date: 2018/10/16 14:04
* @Description:
*/
public class QQProperties extends SocialProperties {
private String providerId = "qq";
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
}
在application.properties配置
guaidan.security.social.qq.app-id= 你自己的 APP ID
guaidan.security.social.qq.app-secret= 你自己的 APP Key
guaidan.security.social.qq.providerId = qq
guaidan.security.social.filterProcessesUrl = /social
注意点 这里根据配置需要将QQ互联中的配置为http[s]://域名/[filterProcessesUrl ]/[providerId ] 例如 http://test.demo.com/social/qq 否则会有匹配不上的问题