爱收集资源网

客户端授权服务器获取访问令牌及资源服务器验证流程解析

爱收集资源网 2024-09-18 22:40

在网络时代中,每一家网站都应重视其用户的登陆体验及安全性问题。当用户返回本网站时,所携带的看似简单的code码实则包含了用户的sessionID,代表了用户的身份及其状态。本文将详细剖析此过程中所存在的安全风险,并提出相应的设计策略以确保用户的安全与便捷性。

https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=授权后跳转的uri
示例:
https://api.weibo.com/oauth2/authorize?
client_id=刚才申请的APP-KEY &
response_type=code&
redirect_uri=http://gulimall.com/success

一、sessionID的意义与重要性

https://api.weibo.com/oauth2/access_token?
client_id=YOUR_CLIENT_ID&
client_secret=YOUR_CLIENT_SECRET&
grant_type=authorization_code&
redirect_uri=YOUR_REGISTERED_REDIRECT_URI&
code=CODE

在用户访问站点之际,系统会生成唯一的sessionID,作为用户的身份标识。凭借着这一特点,系统得以识别和记忆真实用户,提供个性化服务。然而,sessionID的隐私保护非常关键,若被恶意用户窃取,将有可能引发用户数据泄露及账户失窃等问题。

{
    "access_token": "SlAV32hkKG",
    "remind_in": 3600, # 也是声明周期,但将废弃
    "expires_in": 3600 # access_token的生命周期;
}

实际上,对SESSIONID的管理非比寻常。必须保证每一次用户接入,都会产生一个全新的、独特的SESSIONID,并在用户离开后立即失效。同时,对于SESSIONID的存储,要格外小心,防止其存放在不安全的位置,以防在传输过程中被窃取。唯有如此,方能有效保障用户的隐私与安全。

@GetMapping("/weibo/success") // Oath2Controller
public String weiBo(@RequestParam("code") String code, HttpSession session) throws Exception {
    // 根据code换取 Access Token
    Map<String,String> map = new HashMap<>();
    map.put("client_id", "1294828100");
    map.put("client_secret", "a8e8900e15fba6077591cdfa3105af44");
    map.put("grant_type", "authorization_code");
    map.put("redirect_uri", "http://auth.gulimall.com/oauth2.0/weibo/success");
    map.put("code", code);
    Map<String, String> headers = new HashMap<>();
    // 去获取token
    HttpResponse response = HttpUtils.doPost("https://api.weibo.com", "/oauth2/access_token", "post", headers, null, map);
    if(response.getStatusLine().getStatusCode() == 200){
        // 获取响应体: Access Token
        String json = EntityUtils.toString(response.getEntity());
        SocialUser socialUser = JSON.parseObject(json, SocialUser.class);
        // 相当于我们知道了当前是那个用户
        // 1.如果用户是第一次进来 自动注册进来(为当前社交用户生成一个会员信息 以后这个账户就会关联这个账号)
        R login = memberFeignService.login(socialUser);
        if(login.getCode() == 0){
            MemberRsepVo rsepVo = login.getData("data" ,new TypeReference<MemberRsepVo>() {});
            log.info("\n欢迎 [" + rsepVo.getUsername() + "] 使用社交账号登录");
            // 第一次使用session 命令浏览器保存这个用户信息 JESSIONSEID 每次只要访问这个网站就会带上这个cookie
            // 在发卡的时候扩大session作用域 (指定域名为父域名)
            // TODO 1.默认发的当前域的session (需要解决子域session共享问题)
            // TODO 2.使用JSON的方式序列化到redis
            //				new Cookie("JSESSIONID","").setDomain("gulimall.com");
            session.setAttribute(AuthServerConstant.LOGIN_USER, rsepVo);
            // 登录成功 跳回首页
            return "redirect:http://gulimall.com";
        }else{
            return "redirect:http://auth.gulimall.com/login.html";
        }
    }else{
        return "redirect:http://auth.gulimall.com/login.html";
    }
}

二、API调用与用户数据的获取

@RequestMapping("/oauth2/login")
public R login(@RequestBody SocialUser socialUser) {
    MemberEntity entity=memberService.login(socialUser);
    if (entity!=null){
        return R.ok().put("memberEntity",entity);
    }else {
        return R.error();
    }
}
@Override // 已经用code生成了token
public MemberEntity login(SocialUser socialUser) {
    // 微博的uid
    String uid = socialUser.getUid();
    // 1.判断社交用户登录过系统
    MemberDao dao = this.baseMapper;
    MemberEntity entity = dao.selectOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid));
    MemberEntity memberEntity = new MemberEntity();
    if(entity != null){ // 注册过
        // 说明这个用户注册过, 修改它的资料
        // 更新令牌
        memberEntity.setId(entity.getId());
        memberEntity.setAccessToken(socialUser.getAccessToken());
        memberEntity.setExpiresIn(socialUser.getExpiresIn());
        // 更新
        dao.updateById(memberEntity);
        entity.setAccessToken(socialUser.getAccessToken());
        entity.setExpiresIn(socialUser.getExpiresIn());
        entity.setPassword(null);
        return entity;
    }else{ // 没有注册过
        // 2. 没有查到当前社交用户对应的记录 我们就需要注册一个
        HashMap<String, String> map = new HashMap<>();
        map.put("access_token", socialUser.getAccessToken());
        map.put("uid", socialUser.getUid());
        try {
            // 3. 查询当前社交用户账号信息(昵称、性别、头像等)
            HttpResponse response = HttpUtils.doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<>(), map);
            if(response.getStatusLine().getStatusCode() == 200){
                // 查询成功
                String json = EntityUtils.toString(response.getEntity());
                // 这个JSON对象什么样的数据都可以直接获取
                JSONObject jsonObject = JSON.parseObject(json);
                memberEntity.setNickname(jsonObject.getString("name"));
                memberEntity.setUsername(jsonObject.getString("name"));
                memberEntity.setGender("m".equals(jsonObject.getString("gender"))?1:0);
                memberEntity.setCity(jsonObject.getString("location"));
                memberEntity.setJob("自媒体");
                memberEntity.setEmail(jsonObject.getString("email"));
            }
        } catch (Exception e) {
            log.warn("社交登录时远程调用出错 [尝试修复]");
        }
        memberEntity.setStatus(0);
        memberEntity.setCreateTime(new Date());
        memberEntity.setBirth(new Date());
        memberEntity.setLevelId(1L);
        memberEntity.setSocialUid(socialUser.getUid());
        memberEntity.setAccessToken(socialUser.getAccessToken());
        memberEntity.setExpiresIn(socialUser.getExpiresIn());
        // 注册 -- 登录成功
        dao.insert(memberEntity);
        memberEntity.setPassword(null);
        return memberEntity;
    }
}

微博业务24小时下单_微博的业务_微博业务平台

在用户成功登录后,系统将生成唯一的AccessToken,借助此令牌,可调用API获取用户头像及昵称等详细信息。尽管流程看似简易,实则蕴含着严谨的验证机制。每次API调用均需对用户身份进行核实,以确保仅授权用户方可访问其个人信息。

为了确保持续性的同时加强网络的安全性,当今的加密技术可用于对Token进行加密与签名,以确保其在传输过程中的安全。另外,定期更新Token的有效期并及时失效过期的Token,是防范恶意攻击的关键措施。通过这些策略,用户的数据将得到充分的保护。

三、拦截器与安全访问控制

访问首页需验证,Shiro守卫来保障。访问策略权重分,身份明晰方通行。

<dependency>
    <groupId>org.springframework.sessiongroupId>
    <artifactId>spring-session-data-redisartifactId>
dependency>
<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
dependency>

拦截器作为一种高效工具,既可提高系统安全性,又能改善用户体验。对普通用户而言,我们设计了简易便捷的登陆界面;对高级权限用户如管理员等,则实施更为严谨的身份认证机制。如此一来,便能有效防范未经授权的访问,确保系统的安全无忧。

spring.session.store-type=redis
server.servlet.session.timeout=30m
spring.redis.host=192.168.56.10

四、跨系统的用户认证

微博业务24小时下单_微博的业务_微博业务平台

@EnableRedisHttpSession //创建了一个springSessionRepositoryFilter ,负责将原生HttpSession 替换为Spring Session的实现
public class GulimallAuthServerApplication {

当前网络环境下,用户频繁在各类系统间穿梭,因而单点登录(SSO)技术应运而生。当用户成功注册并登录至SSO系统后,即可在各子系统间自由切换,极大地提高了使用便利性。然而,这一设计也引发了全新的安全隐患。

@Configuration
public class GulimallSessionConfig {
    @Bean // redis的json序列化
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
    @Bean // cookie
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("GULISESSIONID"); // cookie的键
        serializer.setDomainName("gulimall.com"); // 扩大session作用域,也就是cookie的有效域
        return serializer;
    }
}

例如,SSO系统的用户在成功登录后,必须通过各个子系统检验所生成的ServiceTicket(ST)。若此环节处理不妥当,可能造成安全隐患,使恶意者有机可乘伪造身份。因此,保证ST的有效性与安全性至关重要,系统需在每次访问时对其进行核实,以确保仅有合法用户得以授权。

@Import({RedisHttpSessionConfiguration.class})
@Configuration( proxyBeanMethods = false)
public @interface EnableRedisHttpSession {

五、ThreadLocal与本地数据共享

public class RedisHttpSessionConfiguration 
    extends SpringHttpSessionConfiguration // 继承
    implements 。。。{
    
    // 后面SessionRepositoryFilter会构造时候自动注入他
    @Bean // 操作session的方法,如getSession()  deleteById()
    public RedisIndexedSessionRepository sessionRepository() {

为提升系统性能,我们采用ThreadLocal以实现用户数据共享。凭借此方式,用户信息可储存在本地内存,从而避免频繁访问远程会话,减轻系统负荷。然而,需谨慎运用,确保数据安全。

public class SpringHttpSessionConfiguration 
    implements ApplicationContextAware {
    @Bean
    public  SessionRepositoryFilter springSessionRepositoryFilter(SessionRepository sessionRepository) { // 注入前面的bean
        SessionRepositoryFilter sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository);
        sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
        return sessionRepositoryFilter;
    }

尽管ThreadLocal有助于提高效率,然而若管理不当,可能引发诸如内存泄露等问题。因此,必须在用户退出环节妥善清除ThreadLocal内存储的数据,以防潜在的安全风险。此外,应精心设计数据共享策略,使系统能够高效运作且保障用户数据安全。

@Override // SessionRepositoryFilter.java
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response, 
                                FilterChain filterChain) {
    
    request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
    //对原生的request、response进行包装
    // SessionRepositoryRequestWrapper.getSession()
    SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
        request, response, this.servletContext);
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
        wrappedRequest, response);
    try {
        filterChain.doFilter(wrappedRequest, wrappedResponse);
    }
    finally {
        wrappedRequest.commitSession();
    }
}

@GetMapping({"/login.html","/","/index","/index.html"}) // auth
public String loginPage(HttpSession session){
    // 从会话从获取loginUser
    Object attribute = session.getAttribute(AuthServerConstant.LOGIN_USER);// "loginUser";
    System.out.println("attribute:"+attribute);
    if(attribute == null){
        return "login";
    }
    System.out.println("已登陆过,重定向到首页");
    return "redirect:http://gulimall.com";
}
@PostMapping("/login") // auth
public String login(UserLoginVo userLoginVo,
                    RedirectAttributes redirectAttributes,
                    HttpSession session){
    // 远程登录
    R r = memberFeignService.login(userLoginVo);
    if(r.getCode() == 0){
        // 登录成功
        MemberRespVo respVo = r.getData("data", new TypeReference<MemberRespVo>() {});
        // 放入session  // key为loginUser
        session.setAttribute(AuthServerConstant.LOGIN_USER, respVo);//loginUser
        log.info("\n欢迎 [" + respVo.getUsername() + "] 登录");
        // 登录成功重定向到首页
        return "redirect:http://gulimall.com";
    }else {
        HashMap<String, String> error = new HashMap<>();
        // 获取错误信息
        error.put("msg", r.getData("msg",new TypeReference<String>(){}));
        redirectAttributes.addFlashAttribute("errors", error);
        return "redirect:http://auth.gulimall.com/login.html";
    }
}

微博的业务