文章目錄
OAuth授權協議簡介
OAuth協議要解決的問題
解決第三方應用的訪問授權問題,用戶不需要通過密碼進行授權,只需要通過token授權即可,保護了密碼泄露的問題。
OAuth協議中的各種角色
服務提供商Provider:提供令牌Token。
- 認證服務器:Authentication Server 認證用戶身份,產生令牌。
- 資源服務器:Resource Server 保存用戶信息,驗證令牌。
資源所有者Resource Owner: 用戶
第三方應用Client:比如qq登錄、微信登錄。
OAuth協議中的授權模式有四種:
- 授權碼模式 (authentication code)
- 簡化模式 (implicit)
- 密碼模式 (resource owner pwssword credentials)
- 客戶端模式 (client credentials)
Spring Social實現QQ登錄
1、實現獲取用戶信息接口
自定義用戶信息實體類:
public class QQUserInfo {
/**
* ret : 0
* msg :
* nickname : Peter
* figureurl : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/30
* figureurl_1 : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/50
* figureurl_2 : http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/100
* figureurl_qq_1 : http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/40
* figureurl_qq_2 : http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/100
* gender : 男
* is_yellow_vip : 1
* vip : 1
* yellow_vip_level : 7
* level : 7
* is_yellow_year_vip : 1
*/
private int ret;
private String msg;
private String nickname;
private String figureurl;
private String figureurl_1;
private String figureurl_2;
private String figureurl_qq_1;
private String figureurl_qq_2;
private String gender;
private String is_yellow_vip;
private String vip;
private String yellow_vip_level;
private String level;
private String is_yellow_year_vip;
public int getRet() {
return ret;
}
public void setRet(int ret) {
this.ret = ret;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getFigureurl() {
return figureurl;
}
public void setFigureurl(String figureurl) {
this.figureurl = figureurl;
}
public String getFigureurl_1() {
return figureurl_1;
}
public void setFigureurl_1(String figureurl_1) {
this.figureurl_1 = figureurl_1;
}
public String getFigureurl_2() {
return figureurl_2;
}
public void setFigureurl_2(String figureurl_2) {
this.figureurl_2 = figureurl_2;
}
public String getFigureurl_qq_1() {
return figureurl_qq_1;
}
public void setFigureurl_qq_1(String figureurl_qq_1) {
this.figureurl_qq_1 = figureurl_qq_1;
}
public String getFigureurl_qq_2() {
return figureurl_qq_2;
}
public void setFigureurl_qq_2(String figureurl_qq_2) {
this.figureurl_qq_2 = figureurl_qq_2;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getIs_yellow_vip() {
return is_yellow_vip;
}
public void setIs_yellow_vip(String is_yellow_vip) {
this.is_yellow_vip = is_yellow_vip;
}
public String getVip() {
return vip;
}
public void setVip(String vip) {
this.vip = vip;
}
public String getYellow_vip_level() {
return yellow_vip_level;
}
public void setYellow_vip_level(String yellow_vip_level) {
this.yellow_vip_level = yellow_vip_level;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getIs_yellow_year_vip() {
return is_yellow_year_vip;
}
public void setIs_yellow_year_vip(String is_yellow_year_vip) {
this.is_yellow_year_vip = is_yellow_year_vip;
}
}
自定義一個獲取用戶信息接口:
public interface QQ {
QQUserInfo getUserInfo() throws JsonProcessingException;
}
獲取用戶信息接口實現:
/**
* 獲取用戶信息
*/
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
/**
* 獲取openId的url
*/
private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
/**
* 獲取用戶信息的url
*/
private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
private String appId;
private String openId;
private ObjectMapper objectMapper = new ObjectMapper();
public QQImpl(String accessToken, String appId) {
// 這句話會自動將accesstoken掛在請求上
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId = appId;
String url = String.format(URL_GET_OPENID, accessToken);
String result = getRestTemplate().getForObject(url, String.class);
System.out.println(result);
this.openId = StringUtils.substring(result,result.indexOf("\"openid\":"), result.lastIndexOf("}"));
}
@Override
public QQUserInfo getUserInfo() throws JsonProcessingException {
String url = String.format(URL_GET_USERINFO, appId, openId);
String result = getRestTemplate().getForObject(url,String.class);
System.out.println(result);
return objectMapper.readValue(result, QQUserInfo.class);
}
}
2、服務提供商
QQ服務提供商調用獲取用戶信息接口和獲取token服務商接口。
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {
private String appId;
/**
* 獲取Authorization Code 授權碼
*/
private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
/**
* 權限自動續期,獲取Access Token
*/
private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";
public QQServiceProvider(String appId,String appSecret) {
super(new OAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN ));
}
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken, appId);
}
}
3、實現QQAdatapter
public class QQAdapter implements ApiAdapter<QQ> {
@Override
public boolean test(QQ qq) {
return true;
}
/**
* 設置ConnectionValues
* @param api
* @param connectionValues
*/
@Override
public void setConnectionValues(QQ api, ConnectionValues connectionValues) {
QQUserInfo userInfo = api.getUserInfo();
connectionValues.setDisplayName(userInfo.getNickname());
connectionValues.setImageUrl(userInfo.getFigureurl_qq_1());
connectionValues.setProfileUrl(null);
connectionValues.setProviderUserId(userInfo.getOpenId());
}
@Override
public UserProfile fetchUserProfile(QQ qq) {
return null;
}
@Override
public void updateStatus(QQ qq, String s) {
// do nothing
}
}
4、實現QQConnectinFactory
public class QQConnectinFactory extends OAuth2ConnectionFactory<QQ> {
public QQConnectinFactory(String providerId, String appId, String appSeret) {
super(providerId, new QQServiceProvider(appId, appSeret), new QQAdapter());
}
}
5、數據連接配置
/**
* spring social 配置
*/
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
// 第三個參數對用戶信息進行加解密
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());
// 設置表前綴
repository.setTablePrefix("imooc_");
return repository;
}
}
6、在JdbcUsersConnectionRepository類中找到sql文件,在數據庫中創建表。
7、登錄驗證
在用戶密碼登錄時,實現了UserDetailsService,進行用於密碼登錄驗證。使用SpringSocial實現登錄驗證需要例外實現SocialUserDetailsService接口的loadUserByUserId方法:
/**
* 用戶認證
*/
@Component
public class MyUserDetailsService implements UserDetailsService, SocialUserDetailsService {
private static final Logger logger = LoggerFactory.getLogger(MyUserDetailsService.class);
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("登錄用戶名:" + username);
// 根據用戶名查找用戶信息
// 根據查找的用戶信息判斷用戶是否被凍結
/**
* 第三個參數是權限集合 ,開發中可以將系統的user類,實現UserDetails進行用戶信息驗證
*/
return new User(username,passwordEncoder.encode("123456"),
true,true,true,true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
@Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
logger.info("社交登錄用戶登錄id:" + userId);
// 根據用戶名查找用戶信息
// 根據查找的用戶信息判斷用戶是否被凍結
/**
* 第三個參數是權限集合 ,開發中可以將系統的user類,實現UserDetails進行用戶信息驗證
*/
return (SocialUserDetails) new User(userId,passwordEncoder.encode("123456"),
true,true,true,true,
AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
8、配置QQ登錄所需的appId,appSecret,ProviderId
9、配置QQConnectionFactory
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.social.qq",name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
QQProperties qqConfig = securityProperties.getSocial().getQq();
return new QQConnectinFactory(qqConfig.getProviderId(), qqConfig.getAppId(),qqConfig.getAppSecret());
}
}
10 、將SpringSocialConfigurer添加到Security的過濾器鏈上。
實例化bean
@Bean
public SpringSocialConfigurer imoocSocialSecurityConfig() {
return new SpringSocialConfigurer();
}
通過httSecurity的apply方法進行配置
.apply(imoocSocialSecurityConfig)