Java realizes WeChat scan code login and realizes authentication and authorization

Java realizes WeChat scan code login and realizes authentication and authorization

1. Login process and principle

1.1 OAuth2 protocol

The website application WeChat login is a WeChat OAuth2.0 authorized login system based on the OAuth2.0 protocol standard. Before performing WeChat OAuth2.0 authorized login access, register a developer account on the WeChat open platform, and have a website application that has been approved, and obtain the corresponding AppID and AppSecret. After applying for WeChat login and passing the review, you can start to receive into the process.

Program flow:

	 +--------+                               +---------------+
     |        |--(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 includes the following roles:

1. Client

It does not store resources itself, and needs to request the resources of the resource server through the authorization of the resource owner, such as: mobile client, browser, etc.

2. Resource owner

Usually a user, but also an application, that is, the owner of the resource.

A indicates that the client requests authorization from the resource owner.

B indicates that the resource owner authorizes the client to access its own user information.

3. Authorization server (also called authentication server)

The authentication server authenticates the resource owner and also authenticates the client and issues tokens.

The C client carries the authorization code to request authentication.

D authenticates by issuing a token.

4. Resource server

A server that stores resources.

E means that the client carries the token to request the resource server to obtain resources.

F indicates that the resource server provides protected resources after the verification token passes.

2.2 WeChat scan code login process

Take the scan code login on the browser as an example:

WeChat scan code login process

Authentication login process:

1. The user applies to log in to the website, scans the WeChat QR code, and requests WeChat to authorize the login;

2. After the user confirms, the WeChat terminal will carry the code to redirect to the website;

3. The website brings the code, appid, and appsecret to apply for an access_token from WeChat;

4. WeChat returns the access_token, and the website takes the access_token to obtain user information from the WeChat server;

5. After the website gets the information, it redirects to the login interface and the login is successful.

2. Code implementation

What do I need to do for this project certification service?

1. It is necessary to define an interface to receive the authorization code issued by WeChat.

2. After receiving the authorization code, call the WeChat interface to apply for a token.

3. Apply for a token and call WeChat to obtain user information

4. Obtain user information and write it into the user center database of this project successfully.

5. Finally, redirect to the browser to log in automatically.

code show as below:

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

Here directly implement the class with 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. Authentication and authorization

The project integrates Spring Security, and the user's permission information needs to be obtained from the user information;

3.1 UserServiceImpl

Rewrote Spring Security's user authentication method to enable access to WeChat login authentication;

authParamsDto authentication parameter definition;

/**
 * @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
}

User extension information definition;

/**
 * @description 用户扩展信息
 */
@Data
public class XcUserExt extends XcUser {
    
    
    //用户权限
    List<String> permissions = new ArrayList<>();
}

The loadUserByUsername() method is rewritten to support WeChat authentication;

@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 interface

public interface AuthService {
    
    

  /**
   * @description 认证方法
   * @param authParamsDto 认证参数
   * @return 用户信息
   */
  UserExt execute(AuthParamsDto authParamsDto);

}

The above method is implemented execute()in the WeChat login service implementation classWxAuthServiceImpl

//微信认证方法
    @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 Customize DaoAuthenticationProvider;

The Spring Security framework defaults to the password verification mode, which is rewritten to be empty so that it no longer verifies the password;

@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 {
    
    

 }

Modify WebSecurityConfigthe class to specify a customdaoAuthenticationProviderCustom

@Autowired
    DaoAuthenticationProviderCustom daoAuthenticationProviderCustom;

    //使用自己定义DaoAuthenticationProviderCustom来代替框架的DaoAuthenticationProvider
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.authenticationProvider(daoAuthenticationProviderCustom);
    }

So far we have modified the authentication process based on Spring Security as follows:

image-20230221184043661

Finish!

Guess you like

Origin blog.csdn.net/dedede001/article/details/129147893