Java微信扫码登录功能并实现认证授权全过程
作者:earlytrain9653
1.登录流程及原理
1.1 OAuth2协议
网站应用微信登录是基于OAuth2.0协议标准构建的微信OAuth2.0授权登录系统。 在进行微信OAuth2.0授权登录接入之前,在微信开放平台注册开发者帐号,并拥有一个已审核通过的网站应用,并获得相应的 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、客户端
本身不存储资源,需要通过资源拥有者的授权去请求资源服务器的资源,比如:手机客户端、浏览器等。
2、资源拥有者
通常为用户,也可以是应用程序,即该资源的拥有者。
A表示客户端请求资源拥有者授权。
B表示资源拥有者授权客户端访问自己的用户信息。
3、授权服务器(也称认证服务器)
认证服务器对资源拥有者进行认证,还会对客户端进行认证并颁发令牌。
C 客户端携带授权码请求认证。
D认证通过颁发令牌。
4、资源服务器
存储资源的服务器。
E表示客户端携带令牌请求资源服务器获取资源。
F表示资源服务器校验令牌通过后提供受保护资源。
2.2 微信扫码登录流程
以浏览器上扫码登录为例:
认证登录流程:
1、用户申请登录网站,扫微信二维码,请求微信授权登录;
2、用户确认后,微信端会携带code重定向到该网站;
3、网站带上code、appid、appsecret向微信端申请access_token;
4、微信返回access_token,网站带上access_token向微信服务端获取用户信息;
5、网站拿到信息后重定向到登陆界面即登陆成功。
2.代码实现
本项目认证服务需要做哪些事?
1、需要定义接口接收微信下发的授权码。
2、收到授权码调用微信接口申请令牌。
3、申请到令牌调用微信获取用户信息
4、获取用户信息成功将其写入本项目用户中心数据库。
5、最后重定向到浏览器自动登录。
代码如下:
2.1 controller
@Controller public class WxLoginController { @Autowired WxAuthServiceImpl wxAuthService; /** * 用户扫码确认登录后进入该接口,收到wx端重定向传过来的授权码,用授权码申请令牌,查询用户信息,写入用户信息 * @param code 微信端返回的授权码 * @param state 用于保持请求和回调的状态,授权请求后原样带回给第三方。 * 该参数可用于防止 csrf 攻击(跨站请求伪造攻击),建议第三方带上该参数, * 可设置为简单的随机数加 session 进行校验 * @return * @throws IOException */ @RequestMapping("/wxLogin") public String wxLogin(String code, String state) throws IOException { //拿授权码申请令牌,查询用户 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实现类;
@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; //拿授权码申请令牌,查询用户 public User wxAuth(String code) { //拿授权码获取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); //添加用户到数据库 User User = currentProxy.addWxUser(userinfo); return User; } @Transactional public User addWxUser(Map userInfo_map){ //先取出unionid String unionid = (String) userInfo_map.get("unionid"); //根据unionid查询数据库 User User = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getWxUnionid, unionid)); if(User!=null){ //该用户在系统存在 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");//学生类型 User.setStatus("1");//用户状态 User.setCreateTime(LocalDateTime.now()); userMapper.insert(User); UserRole UserRole = new UserRole(); UserRole.setId(UUID.randomUUID().toString()); UserRole.setUserId(id); UserRole.setRoleId("17");//学生角色 userRoleMapper.insert(UserRole); return User; } //请求微信获取令牌 /** * 微信接口响应结果 * { * "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); //得到响应串 String responseString = response.getBody(); //将json串转成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(); //将结果转成map Map map = JSON.parseObject(body, Map.class); return map; } }
3.认证授权
项目集成了Spring Security,还需从用户信息中获取该用户的权限信息;
3.1 UserServiceImpl
重写了Spring Security的用户认证方式,使其接入微信登录认证;
authParamsDto 认证参数定义;
/** * @description 统一认证入口后统一提交的数据 */ @Data public class AuthParamsDto { private String username; //用户名 private String password; //域 用于扩展 private String cellphone;//手机号 private String checkcode;//验证码 private String checkcodekey;//验证码key private String authType; // 认证的类型 password:用户名密码模式类型 sms:短信模式类型 private Map<String, Object> payload = new HashMap<>();//附加数据,作为扩展,不同认证类型可拥有不同的附加数据。如认证类型为短信时包含smsKey : sms:3d21042d054548b08477142bbca95cfa; 所有情况下都包含clientId }
用户扩展信息定义;
/** * @description 用户扩展信息 */ @Data public class XcUserExt extends XcUser { //用户权限 List<String> permissions = new ArrayList<>(); }
loadUserByUsername()方法重写,使其支持微信认证;
@Slf4j @Service public class UserServiceImpl implements UserDetailsService { @Autowired UserMapper userMapper; @Autowired ApplicationContext applicationContext; @Autowired MenuMapper menuMapper;//菜单权限mapper //传入的是AuthParamsDto的json串 @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { AuthParamsDto authParamsDto = null; try { //将认证参数转为AuthParamsDto类型 authParamsDto = JSON.parseObject(s, AuthParamsDto.class); } catch (Exception e) { log.info("认证请求不符合项目要求:{}",s); throw new RuntimeException("认证请求数据格式不对"); } //认证方式, String authType = authParamsDto.getAuthType(); //从spring容器中拿具体的认证bean实例 AuthService authService = applicationContext.getBean(authType + "_authservice", AuthService.class); //开始认证,认证成功拿到用户信息 UserExt UserExt = authService.execute(authParamsDto); return getUserPrincipal(UserExt); } //根据UserExt对象构造一个UserDetails对象 /** * @description 查询用户信息 * @param user 用户id,主键 * @return 用户信息 */ public UserDetails getUserPrincipal(UserExt user){ //权限列表,存放的用户权限 List<String> permissionList = new ArrayList<>(); //根据用户id查询数据库中他的权限 List<Menu> Menus = menuMapper.selectPermissionByUserId(user.getId()); Menus.forEach(menu->{ permissionList.add(menu.getCode()); }); if(permissionList.size()==0){ //用户权限,如果不加报Cannot pass a null GrantedAuthority collection permissionList.add("test"); } String[] authorities= permissionList.toArray(new String[0]); //原来存的是账号,现在扩展为用户的全部信息(密码不要放) 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 认证方法 * @param authParamsDto 认证参数 * @return 用户信息 */ UserExt execute(AuthParamsDto authParamsDto); }
上述execute()
方法在微信登录服务实现类WxAuthServiceImpl
中实现
//微信认证方法 @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框架默认是密码校验模式,将其重写为空,使其不再校验密码;
@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认证流程修改为如下:
完成!
总结
到此这篇关于Java微信扫码登录功能并实现认证授权的文章就介绍到这了,更多相关Java微信扫码登录认证授权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!