Java微信掃碼登錄功能并實(shí)現(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)。 在進(jìn)行微信OAuth2.0授權(quán)登錄接入之前,在微信開放平臺注冊開發(fā)者帳號,并擁有一個(gè)已審核通過的網(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ù)器的資源,比如:手機(jī)客戶端、瀏覽器等。
2、資源擁有者
通常為用戶,也可以是應(yīng)用程序,即該資源的擁有者。
A表示客戶端請求資源擁有者授權(quán)。
B表示資源擁有者授權(quán)客戶端訪問自己的用戶信息。
3、授權(quán)服務(wù)器(也稱認(rèn)證服務(wù)器)
認(rèn)證服務(wù)器對資源擁有者進(jìn)行認(rèn)證,還會對客戶端進(jìn)行認(rèn)證并頒發(fā)令牌。
C 客戶端攜帶授權(quán)碼請求認(rèn)證。
D認(rèn)證通過頒發(fā)令牌。
4、資源服務(wù)器
存儲資源的服務(wù)器。
E表示客戶端攜帶令牌請求資源服務(wù)器獲取資源。
F表示資源服務(wù)器校驗(yàn)令牌通過后提供受保護(hù)資源。
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.代碼實(shí)現(xiàn)
本項(xiàng)目認(rèn)證服務(wù)需要做哪些事?
1、需要定義接口接收微信下發(fā)的授權(quán)碼。
2、收到授權(quán)碼調(diào)用微信接口申請令牌。
3、申請到令牌調(diào)用微信獲取用戶信息
4、獲取用戶信息成功將其寫入本項(xiàng)目用戶中心數(shù)據(jù)庫。
5、最后重定向到瀏覽器自動(dòng)登錄。
代碼如下:
2.1 controller
@Controller public class WxLoginController { @Autowired WxAuthServiceImpl wxAuthService; /** * 用戶掃碼確認(rèn)登錄后進(jìn)入該接口,收到wx端重定向傳過來的授權(quán)碼,用授權(quán)碼申請令牌,查詢用戶信息,寫入用戶信息 * @param code 微信端返回的授權(quán)碼 * @param state 用于保持請求和回調(diào)的狀態(tài),授權(quán)請求后原樣帶回給第三方。 * 該參數(shù)可用于防止 csrf 攻擊(跨站請求偽造攻擊),建議第三方帶上該參數(shù), * 可設(shè)置為簡單的隨機(jī)數(shù)加 session 進(jìn)行校驗(yàn) * @return * @throws IOException */ @RequestMapping("/wxLogin") public String wxLogin(String code, String state) throws IOException { //拿授權(quán)碼申請令牌,查詢用戶 XcUser xcUser = wxAuthService.wxAuth(code); if(xcUser == null){ //重定向到一個(gè)錯(cuò)誤頁面 return "redirect:http://www.xxxxxxx.com/error.html"; }else{ String username = xcUser.getUsername(); //重定向到登錄頁面,自動(dòng)登錄 return "redirect:http://www.xxxxxxx.com/sign.html?username="+username+"&authType=wx"; } } }
2.2 WxAuthServiceImpl
這里直接用service實(shí)現(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)
項(xiàng)目集成了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; //域 用于擴(kuò)展 private String cellphone;//手機(jī)號 private String checkcode;//驗(yàn)證碼 private String checkcodekey;//驗(yàn)證碼key private String authType; // 認(rèn)證的類型 password:用戶名密碼模式類型 sms:短信模式類型 private Map<String, Object> payload = new HashMap<>();//附加數(shù)據(jù),作為擴(kuò)展,不同認(rèn)證類型可擁有不同的附加數(shù)據(jù)。如認(rèn)證類型為短信時(shí)包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情況下都包含clientId }
用戶擴(kuò)展信息定義;
/** * @description 用戶擴(kuò)展信息 */ @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)證請求不符合項(xiàng)目要求:{}",s); throw new RuntimeException("認(rèn)證請求數(shù)據(jù)格式不對"); } //認(rèn)證方式, String authType = authParamsDto.getAuthType(); //從spring容器中拿具體的認(rèn)證bean實(shí)例 AuthService authService = applicationContext.getBean(authType + "_authservice", AuthService.class); //開始認(rèn)證,認(rèn)證成功拿到用戶信息 UserExt UserExt = authService.execute(authParamsDto); return getUserPrincipal(UserExt); } //根據(jù)UserExt對象構(gòu)造一個(gè)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)限,如果不加報(bào)Cannot pass a null GrantedAuthority collection permissionList.add("test"); } String[] authorities= permissionList.toArray(new String[0]); //原來存的是賬號,現(xiàn)在擴(kuò)展為用戶的全部信息(密碼不要放) 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ù)實(shí)現(xiàn)類WxAuthServiceImpl
中實(shí)現(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)是密碼校驗(yàn)?zāi)J剑瑢⑵渲貙憺榭?,使其不再校?yàn)密碼;
@Slf4j @Component public class DaoAuthenticationProviderCustom extends DaoAuthenticationProvider { @Autowired @Override public void setUserDetailsService(UserDetailsService userDetailsService) { super.setUserDetailsService(userDetailsService); } @Override //不再校驗(yàn)密碼 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微信掃碼登錄功能并實(shí)現(xiàn)認(rèn)證授權(quán)的文章就介紹到這了,更多相關(guān)Java微信掃碼登錄認(rèn)證授權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java版數(shù)獨(dú)游戲界面實(shí)現(xiàn)(二)
這篇文章主要為大家詳細(xì)介紹了java版數(shù)獨(dú)游戲界面實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法
這篇文章主要介紹了詳解IDEA啟動(dòng)多個(gè)微服務(wù)的配置方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01SpringBoot實(shí)現(xiàn)異步任務(wù)的項(xiàng)目實(shí)踐
本文將使用SpringBoot 去實(shí)現(xiàn)異步之間的調(diào)用,提高系統(tǒng)的并發(fā)性能、用戶體驗(yàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10eclipse輸出Hello World的實(shí)現(xiàn)方法
這篇文章主要介紹了eclipse輸出Hello World的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11用Java實(shí)現(xiàn)簡單ATM機(jī)功能
這篇文章主要為大家詳細(xì)介紹了用Java實(shí)現(xiàn)簡單ATM機(jī)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01解決springboot中配置過濾器以及可能出現(xiàn)的問題
這篇文章主要介紹了解決springboot中配置過濾器以及可能出現(xiàn)的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09