Java微信掃碼登錄功能并實現(xiàn)認(rèn)證授權(quán)全過程
1.登錄流程及原理
1.1 OAuth2協(xié)議
網(wǎng)站應(yīng)用微信登錄是基于OAuth2.0協(xié)議標(biāo)準(zhǔn)構(gòu)建的微信OAuth2.0授權(quán)登錄系統(tǒng)。 在進行微信OAuth2.0授權(quán)登錄接入之前,在微信開放平臺注冊開發(fā)者帳號,并擁有一個已審核通過的網(wǎng)站應(yīng)用,并獲得相應(yīng)的 AppID 和 AppSecret,申請微信登錄且通過審核后,可開始接入流程。
方案流程:
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
OAuth2包括以下角色:
1、客戶端
本身不存儲資源,需要通過資源擁有者的授權(quán)去請求資源服務(wù)器的資源,比如:手機客戶端、瀏覽器等。
2、資源擁有者
通常為用戶,也可以是應(yīng)用程序,即該資源的擁有者。
A表示客戶端請求資源擁有者授權(quán)。
B表示資源擁有者授權(quán)客戶端訪問自己的用戶信息。
3、授權(quán)服務(wù)器(也稱認(rèn)證服務(wù)器)
認(rèn)證服務(wù)器對資源擁有者進行認(rèn)證,還會對客戶端進行認(rèn)證并頒發(fā)令牌。
C 客戶端攜帶授權(quán)碼請求認(rèn)證。
D認(rèn)證通過頒發(fā)令牌。
4、資源服務(wù)器
存儲資源的服務(wù)器。
E表示客戶端攜帶令牌請求資源服務(wù)器獲取資源。
F表示資源服務(wù)器校驗令牌通過后提供受保護資源。
2.2 微信掃碼登錄流程
以瀏覽器上掃碼登錄為例:

認(rèn)證登錄流程:
1、用戶申請登錄網(wǎng)站,掃微信二維碼,請求微信授權(quán)登錄;
2、用戶確認(rèn)后,微信端會攜帶code重定向到該網(wǎng)站;
3、網(wǎng)站帶上code、appid、appsecret向微信端申請access_token;
4、微信返回access_token,網(wǎng)站帶上access_token向微信服務(wù)端獲取用戶信息;
5、網(wǎng)站拿到信息后重定向到登陸界面即登陸成功。
2.代碼實現(xiàn)
本項目認(rèn)證服務(wù)需要做哪些事?
1、需要定義接口接收微信下發(fā)的授權(quán)碼。
2、收到授權(quán)碼調(diào)用微信接口申請令牌。
3、申請到令牌調(diào)用微信獲取用戶信息
4、獲取用戶信息成功將其寫入本項目用戶中心數(shù)據(jù)庫。
5、最后重定向到瀏覽器自動登錄。
代碼如下:
2.1 controller
@Controller
public class WxLoginController {
@Autowired
WxAuthServiceImpl wxAuthService;
/**
* 用戶掃碼確認(rèn)登錄后進入該接口,收到wx端重定向傳過來的授權(quán)碼,用授權(quán)碼申請令牌,查詢用戶信息,寫入用戶信息
* @param code 微信端返回的授權(quán)碼
* @param state 用于保持請求和回調(diào)的狀態(tài),授權(quán)請求后原樣帶回給第三方。
* 該參數(shù)可用于防止 csrf 攻擊(跨站請求偽造攻擊),建議第三方帶上該參數(shù),
* 可設(shè)置為簡單的隨機數(shù)加 session 進行校驗
* @return
* @throws IOException
*/
@RequestMapping("/wxLogin")
public String wxLogin(String code, String state) throws IOException {
//拿授權(quán)碼申請令牌,查詢用戶
XcUser xcUser = wxAuthService.wxAuth(code);
if(xcUser == null){
//重定向到一個錯誤頁面
return "redirect:http://www.xxxxxxx.com/error.html";
}else{
String username = xcUser.getUsername();
//重定向到登錄頁面,自動登錄
return "redirect:http://www.xxxxxxx.com/sign.html?username="+username+"&authType=wx";
}
}
}
2.2 WxAuthServiceImpl
這里直接用service實現(xiàn)類;
@Service("wx_authservice")
public class WxAuthServiceImpl implements AuthService {
@Autowired
UserMapper userMapper;
@Value("${weixin.appid}")
String appid;
@Value("${weixin.secret}")
String secret;
@Autowired
RestTemplate restTemplate;
@Autowired
UserRoleMapper userRoleMapper;
@Autowired
WxAuthServiceImpl currentProxy;
//拿授權(quán)碼申請令牌,查詢用戶
public User wxAuth(String code) {
//拿授權(quán)碼獲取access_token
Map<String, String> access_token_map = getAccess_token(code);
System.out.println(access_token_map);
//得到令牌
String access_token = access_token_map.get("access_token");
//得到openid
String openid = access_token_map.get("openid");
//拿令牌獲取用戶信息
Map<String, String> userinfo = getUserinfo(access_token, openid);
System.out.println(userinfo);
//添加用戶到數(shù)據(jù)庫
User User = currentProxy.addWxUser(userinfo);
return User;
}
@Transactional
public User addWxUser(Map userInfo_map){
//先取出unionid
String unionid = (String) userInfo_map.get("unionid");
//根據(jù)unionid查詢數(shù)據(jù)庫
User User = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getWxUnionid, unionid));
if(User!=null){
//該用戶在系統(tǒng)存在
return User;
}
User = new User();
//用戶id
String id = UUID.randomUUID().toString();
User.setId(id);
User.setWxUnionid(unionid);
//記錄從微信得到的昵稱
User.setNickname(userInfo_map.get("nickname").toString());
User.setUserpic(userInfo_map.get("headimgurl").toString());
User.setName(userInfo_map.get("nickname").toString());
User.setUsername(unionid);
User.setPassword(unionid);
User.setUtype("101001");//學(xué)生類型
User.setStatus("1");//用戶狀態(tài)
User.setCreateTime(LocalDateTime.now());
userMapper.insert(User);
UserRole UserRole = new UserRole();
UserRole.setId(UUID.randomUUID().toString());
UserRole.setUserId(id);
UserRole.setRoleId("17");//學(xué)生角色
userRoleMapper.insert(UserRole);
return User;
}
//請求微信獲取令牌
/**
* 微信接口響應(yīng)結(jié)果
* {
* "access_token":"ACCESS_TOKEN",
* "expires_in":7200,
* "refresh_token":"REFRESH_TOKEN",
* "openid":"OPENID",
* "scope":"SCOPE",
* "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
* }
*/
private Map<String, String> getAccess_token(String code) {
String url_template = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
String url = String.format(url_template, appid, secret, code);
//請求微信獲取令牌
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, null, String.class);
System.out.println(response);
//得到響應(yīng)串
String responseString = response.getBody();
//將json串轉(zhuǎn)成map
Map map = JSON.parseObject(responseString, Map.class);
return map;
}
//攜帶令牌查詢用戶信息
//http請求方式: GET
//https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
/**
{
"openid":"OPENID",
"nickname":"NICKNAME",
"sex":1,
"province":"PROVINCE",
"city":"CITY",
"country":"COUNTRY",
"headimgurl": "https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJfHe/0",
"privilege":[
"PRIVILEGE1",
"PRIVILEGE2"
],
"unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
}
*/
private Map<String,String> getUserinfo(String access_token,String openid) {
//請求微信查詢用戶信息
String url_template = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s";
String url = String.format(url_template,access_token,openid);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
String body = response.getBody();
//將結(jié)果轉(zhuǎn)成map
Map map = JSON.parseObject(body, Map.class);
return map;
}
}
3.認(rèn)證授權(quán)
項目集成了Spring Security,還需從用戶信息中獲取該用戶的權(quán)限信息;
3.1 UserServiceImpl
重寫了Spring Security的用戶認(rèn)證方式,使其接入微信登錄認(rèn)證;
authParamsDto 認(rèn)證參數(shù)定義;
/**
* @description 統(tǒng)一認(rèn)證入口后統(tǒng)一提交的數(shù)據(jù)
*/
@Data
public class AuthParamsDto {
private String username; //用戶名
private String password; //域 用于擴展
private String cellphone;//手機號
private String checkcode;//驗證碼
private String checkcodekey;//驗證碼key
private String authType; // 認(rèn)證的類型 password:用戶名密碼模式類型 sms:短信模式類型
private Map<String, Object> payload = new HashMap<>();//附加數(shù)據(jù),作為擴展,不同認(rèn)證類型可擁有不同的附加數(shù)據(jù)。如認(rèn)證類型為短信時包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情況下都包含clientId
}
用戶擴展信息定義;
/**
* @description 用戶擴展信息
*/
@Data
public class XcUserExt extends XcUser {
//用戶權(quán)限
List<String> permissions = new ArrayList<>();
}
loadUserByUsername()方法重寫,使其支持微信認(rèn)證;
@Slf4j
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
UserMapper userMapper;
@Autowired
ApplicationContext applicationContext;
@Autowired
MenuMapper menuMapper;//菜單權(quán)限mapper
//傳入的是AuthParamsDto的json串
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
AuthParamsDto authParamsDto = null;
try {
//將認(rèn)證參數(shù)轉(zhuǎn)為AuthParamsDto類型
authParamsDto = JSON.parseObject(s, AuthParamsDto.class);
} catch (Exception e) {
log.info("認(rèn)證請求不符合項目要求:{}",s);
throw new RuntimeException("認(rèn)證請求數(shù)據(jù)格式不對");
}
//認(rèn)證方式,
String authType = authParamsDto.getAuthType();
//從spring容器中拿具體的認(rèn)證bean實例
AuthService authService = applicationContext.getBean(authType + "_authservice", AuthService.class);
//開始認(rèn)證,認(rèn)證成功拿到用戶信息
UserExt UserExt = authService.execute(authParamsDto);
return getUserPrincipal(UserExt);
}
//根據(jù)UserExt對象構(gòu)造一個UserDetails對象
/**
* @description 查詢用戶信息
* @param user 用戶id,主鍵
* @return 用戶信息
*/
public UserDetails getUserPrincipal(UserExt user){
//權(quán)限列表,存放的用戶權(quán)限
List<String> permissionList = new ArrayList<>();
//根據(jù)用戶id查詢數(shù)據(jù)庫中他的權(quán)限
List<Menu> Menus = menuMapper.selectPermissionByUserId(user.getId());
Menus.forEach(menu->{
permissionList.add(menu.getCode());
});
if(permissionList.size()==0){
//用戶權(quán)限,如果不加報Cannot pass a null GrantedAuthority collection
permissionList.add("test");
}
String[] authorities= permissionList.toArray(new String[0]);
//原來存的是賬號,現(xiàn)在擴展為用戶的全部信息(密碼不要放)
user.setPassword(null);
String jsonString = JSON.toJSONString(user);
UserDetails userDetails = User.withUsername(jsonString).password("").authorities(authorities).build();
return userDetails;
}
}
3.2 service接口
public interface AuthService {
/**
* @description 認(rèn)證方法
* @param authParamsDto 認(rèn)證參數(shù)
* @return 用戶信息
*/
UserExt execute(AuthParamsDto authParamsDto);
}
上述execute()方法在微信登錄服務(wù)實現(xiàn)類WxAuthServiceImpl中實現(xiàn)
//微信認(rèn)證方法
@Override
public UserExt execute(AuthParamsDto authParamsDto) {
//獲取賬號
String username = authParamsDto.getUsername();
User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUsername,username));
if(user==null){
throw new RuntimeException("用戶不存在");
}
UserExt userExt = new UserExt();
BeanUtils.copyProperties(user, userExt);
return userExt;
}
3.3 自定義DaoAuthenticationProvider;
SpringSecurity框架默認(rèn)是密碼校驗?zāi)J剑瑢⑵渲貙憺榭?,使其不再校驗密碼;
@Slf4j
@Component
public class DaoAuthenticationProviderCustom extends DaoAuthenticationProvider {
@Autowired
@Override
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
@Override
//不再校驗密碼
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
}
修改WebSecurityConfig類指定自定義的daoAuthenticationProviderCustom
@Autowired
DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;
//使用自己定義DaoAuthenticationProviderCustom來代替框架的DaoAuthenticationProvider
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProviderCustom);
}
至此我們基于Spring Security認(rèn)證流程修改為如下:

完成!
總結(jié)
到此這篇關(guān)于Java微信掃碼登錄功能并實現(xiàn)認(rèn)證授權(quán)的文章就介紹到這了,更多相關(guān)Java微信掃碼登錄認(rèn)證授權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實現(xiàn)異步任務(wù)的項目實踐
本文將使用SpringBoot 去實現(xiàn)異步之間的調(diào)用,提高系統(tǒng)的并發(fā)性能、用戶體驗,具有一定的參考價值,感興趣的可以了解一下2023-10-10
eclipse輸出Hello World的實現(xiàn)方法
這篇文章主要介紹了eclipse輸出Hello World的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
解決springboot中配置過濾器以及可能出現(xiàn)的問題
這篇文章主要介紹了解決springboot中配置過濾器以及可能出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09

