当前位置: 首页 > news >正文

微信小程序登录JAVA后台

代码地址如下:
http://www.demodashi.com/demo/12736.html

登录流程时序登录流程时序

api-login.jpg?t=2018315

具体的登录说明查看 小程序官方API

项目的结构图:

项目结构

springboot项目搭建

使用idea作为开发工具,由gradle构建项目,搭建springboot项目,对这块儿不熟悉的可以自行去学习,此处不多赘述。下面是核心的配置文件。application.yml中配置springboot默认的参数,application.properties配置自定义的参数,可以统一配置在一个文件中,依据个人习惯。

buidle.gradle配置

buildscript {
    ext {
        springBootVersion = '1.5.10.RELEASE'
    }
    repositories {
        mavenLocal()
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'org.springframework.boot'

group = 'xin.yangmj'
version = '1.0.1'
sourceCompatibility = 1.8

repositories {
    mavenLocal()
    maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-cache')
    compile('org.springframework.boot:spring-boot-starter-data-redis')
    compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('mysql:mysql-connector-java')
    compile('org.springframework.security:spring-security-test')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7'
}

application.yml

logging:
  level:
    root: DEBUG

spring:
    datasource:
        url: jdbc:mysql://localhost/remindme?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver

    redis:
        host: localhost
        password:
        port: 6379

mybatis:
    mapperLocations: classpath:mapper/*.xml
    configuration:
        mapUnderscoreToCamelCase: true
        default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler

application.properties

# JWT相关配置
jwt.header=Authorization
# 过期时间
jwt.expiration=864000
# 注意有一个空格
jwt.tokenHead=Bearer 

# wechat Auth
auth.wechat.sessionHost=https://api.weixin.qq.com/sns/jscode2session
auth.wechat.appId=***
auth.wechat.secret=***
auth.wechat.grantType=authorization_code

权限相关的配置

WebSecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntryPoint unauthorizedHandler;

    @Bean
    public ThirdSessionAuthFilter authenticationTokenFilterBean() throws Exception {
        return new ThirdSessionAuthFilter();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // 由于使用的是JWT,我们这里不需要csrf
                .csrf().disable()
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                // 允许对test的无授权访问
                .antMatchers(HttpMethod.GET, "/test").permitAll()
                // 对于获取token的rest api要允许匿名访问
                .antMatchers("/auth").permitAll();

        // 添加本地地三方session filter
        httpSecurity
                .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

        // 禁用缓存
        httpSecurity.headers().cacheControl();
    }

}

ThirdSessionAuthFilter.java

@Component
public class ThirdSessionAuthFilter extends OncePerRequestFilter {

    @Value("${jwt.header}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ConsumerMapper consumerMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        //获取请求头部分的Authorization
        String authHeader = request.getHeader(this.tokenHeader);
        //如果请求路径为微信通知后台支付结果则不需要token(之后会在具体的controller中,对双方签名进行验证防钓鱼)
        String url = request.getRequestURI().substring(request.getContextPath().length());

        if (url.equals("/auth") || url.equals("/test")) {
            chain.doFilter(request, response);
            return;
        }

        if (null == authHeader || !authHeader.startsWith("Bearer")) {
            throw new RuntimeException("非法访问用户");
        }
        // The part after "Bearer "
        final String thirdSessionId = authHeader.substring(tokenHead.length());
        String wxSessionObj = stringRedisTemplate.opsForValue().get(thirdSessionId);
        if (StringUtils.isEmpty(wxSessionObj)) {
            throw new RuntimeException("用户身份已过期");
        }

        // 设置当前登录用户
        try (AppContext appContext = new AppContext(wxSessionObj.substring(wxSessionObj.indexOf("#") + 1))) {
            chain.doFilter(request, response);
        }
    }

}

AppContext.java

public class AppContext implements AutoCloseable {

    private static final ThreadLocal<String> CURRENT_CONSUMER_WECHAT_OPENID = new ThreadLocal<>();

    public AppContext(String wechatOpenid) {
        CURRENT_CONSUMER_WECHAT_OPENID.set(wechatOpenid);
    }

    @Override
    public void close() {
        CURRENT_CONSUMER_WECHAT_OPENID.remove();
    }

    public static String getCurrentUserWechatOpenId() {
        return CURRENT_CONSUMER_WECHAT_OPENID.get();
    }

}

JwtAuthenticationEntryPoint.java

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }

}

WechatAuthProperties.java

@Component
public class WechatAuthProperties {

    @Value("${auth.wechat.sessionHost}")
    private String sessionHost;

    @Value("${auth.wechat.appId}")
    private String appId;

    @Value("${auth.wechat.secret}")
    private String secret;

    @Value("${auth.wechat.grantType}")
    private String grantType;
    
    //省略getter setter

}

相关实体类对象

public class AccountDto {
    private Long id;
    private String username;
    private Long phone;
    private Gender gender;
    private String vcode;
    private String password;
    private String promotionCode;
    private String InvitationCode;
    private String clientAssertion;
    private String code;
    
    //省略 getter setter
}

Consumer.java

public class Consumer {

    private Long id;
    private String username;
    private String wechatOpenid;
    private Long phone;
    private String nickname;
    private String avatarUrl;
    private Gender gender;
    private String email;
    private Long lastLoginTime;
    private Boolean deleted;
    private Long createdBy;
    private Long createdAt;
    private Long updatedBy;
    private Long updatedAt;
    // 省略 gettter setter
}

Gender.java

public enum Gender {
    UNKNOW(0, "未知"),
    MAN(1, "先生"),
    WOMAN(2, "女士");

    private Byte value;
    private String name;

    Gender(int value, String name) {
        this.value = (byte)value;
        this.name = name;
    }

    public Byte getValue() {
        return this.value;
    }

    public String getName() {
        return this.name;
    }
}
  • API接口类
@RestController
public class AuthEndpoint {

    @Value("${jwt.header}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private WechatService wechatService;

    @GetMapping("/test")
    public String test() {
        return "test_success";
    }

    @GetMapping("/testAuth")
    public String testAuth() {
        return "testAuth_success";
    }

    @PostMapping("/auth")
    public ResponseEntity<WechatAuthenticationResponse> createAuthenticationToken(@RequestBody AccountDto accountDto)
            throws AuthenticationException {
        WechatAuthenticationResponse jwtResponse = wechatService.wechatLogin(accountDto.getCode());
        return ResponseEntity.ok(jwtResponse);
    }

    @PostMapping("/updateConsumerInfo")
    public void updateConsumerInfo(@RequestBody Consumer consumer) {
        wechatService.updateConsumerInfo(consumer);
    }

}

注册核心流程

@Service
public class WechatService {

    private static final Logger LOGGER = LoggerFactory.getLogger(WechatService.class);

    @Autowired
    private ConsumerMapper consumerMapper;

    /**
     * 服务器第三方session有效时间,单位秒, 默认1天
     */
    private static final Long EXPIRES = 86400L;

    private RestTemplate wxAuthRestTemplate = new RestTemplate();

    @Autowired
    private WechatAuthProperties wechatAuthProperties;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public WechatAuthenticationResponse wechatLogin(String code) {
        WechatAuthCodeResponse response = getWxSession(code);

        String wxOpenId = response.getOpenid();
        String wxSessionKey = response.getSessionKey();
        Consumer consumer = new Consumer();
        consumer.setWechatOpenid(wxOpenId);
        loginOrRegisterConsumer(consumer);

        Long expires = response.getExpiresIn();
        String thirdSession = create3rdSession(wxOpenId, wxSessionKey, expires);
        return new WechatAuthenticationResponse(thirdSession);
    }

    public WechatAuthCodeResponse getWxSession(String code) {
        LOGGER.info(code);
        String urlString = "?appid={appid}&secret={srcret}&js_code={code}&grant_type={grantType}";
        String response = wxAuthRestTemplate.getForObject(
                wechatAuthProperties.getSessionHost() + urlString, String.class,
                wechatAuthProperties.getAppId(),
                wechatAuthProperties.getSecret(),
                code,
                wechatAuthProperties.getGrantType());
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectReader reader = objectMapper.readerFor(WechatAuthCodeResponse.class);
        WechatAuthCodeResponse res;
        try {
            res = reader.readValue(response);
        } catch (IOException e) {
            res = null;
            LOGGER.error("反序列化失败", e);
        }
        LOGGER.info(response);
        if (null == res) {
            throw new RuntimeException("调用微信接口失败");
        }
        if (res.getErrcode() != null) {
            throw new RuntimeException(res.getErrmsg());
        }
        res.setExpiresIn(res.getExpiresIn() != null ? res.getExpiresIn() : EXPIRES);
        return res;
    }

    public String create3rdSession(String wxOpenId, String wxSessionKey, Long expires) {
        String thirdSessionKey = RandomStringUtils.randomAlphanumeric(64);
        StringBuffer sb = new StringBuffer();
        sb.append(wxSessionKey).append("#").append(wxOpenId);

        stringRedisTemplate.opsForValue().set(thirdSessionKey, sb.toString(), expires, TimeUnit.SECONDS);
        return thirdSessionKey;
    }

    private void loginOrRegisterConsumer(Consumer consumer) {
        Consumer consumer1 = consumerMapper.findConsumerByWechatOpenid(consumer.getWechatOpenid());
        if (null == consumer1) {
            consumerMapper.insertConsumer(consumer);
        }
    }

    public void updateConsumerInfo(Consumer consumer) {
        Consumer consumerExist = consumerMapper.findConsumerByWechatOpenid(AppContext.getCurrentUserWechatOpenId());
        consumerExist.setUpdatedBy(1L);
        consumerExist.setUpdatedAt(System.currentTimeMillis());
        consumerExist.setGender(consumer.getGender());
        consumerExist.setAvatarUrl(consumer.getAvatarUrl());
        consumerExist.setWechatOpenid(consumer.getWechatOpenid());
        consumerExist.setEmail(consumer.getEmail());
        consumerExist.setNickname(consumer.getNickname());
        consumerExist.setPhone(consumer.getPhone());
        consumerExist.setUsername(consumer.getUsername());
        consumerMapper.updateConsumer(consumerExist);
    }

}

微信小程序代码片段

wx.login() 获取code,然后携带code发送请求到自己服务端,获取登录信息。然后 wx.getUserInfo() 获取用户的基本信息,例如:昵称、头像等,上传本地服务器保存用户基本信息。

    // 登录
    wx.login({
      success: function(res) {
        if (res.code) {
          wx.request({
            url: "http://localhost:8080/auth",
            data: {
              code: res.code
            },
            method: "POST",
            header: {
              'content-type': 'application/json',
            },
            success: function (res) {
              console.log(res.data.access_token);
              var token = res.data.access_token;
              
              wx.getUserInfo({
                success: res => {              
                  // 保存用户信息到服务端
                  wx.request({
                    url: "http://localhost:8080/updateConsumerInfo",
                    data: res.userInfo,
                    method: "POST",
                    header: {
                      'Authorization': 'Bearer ' + token,
                      'content-type': 'application/json',
                    },
                    success: function (res) {
                      console.log("success");
                    },
                    fail: function (error) {
                      console.log(error);
                    }
                  })
                }
              })

            },
            fail: function (error) {
              console.log(error);
            }
          })
        } else {
          console.log("error code " + res.errMsg);
        }
      }
    })

效果展示

  • 刷新微信小程序缓存,编译使发送请求
    Z2904SBtpht1pmr9dYe.png

  • 发送登录请求,完成后获取到 access_token
    h4OWmn4NKKb1g8ZdBKM.png

  • 发送获取用户信息请求
    vcUk7dU9u1nyxqdOwbt.png

  • 小程序请求本地服务器登录接口
    ZJ9jigS0YnoyvRjOwCi.png

  • 本地服务器请求微信服务器登录接口
    XeweBaYrSQ5jGO1TeKE.png

  • 小程序请求本地服务器更新用户信息接口
    SH6gJiYX6AmCOXHlwy7.png

  • redis保存会话信息
    kQ98j6DbDMMZMhgdFrC.png

  • mysql数据库存储用户信息
    UwHA69wKUF2FmoJOXsS.png微信小程序登录JAVA后台

代码地址如下:
http://www.demodashi.com/demo/12736.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

相关文章:

  • yii2.0 验证码
  • Weakpass
  • MySQL基础知识之DDL操作
  • ubuntu LNMP nginx 隐藏index.php 配置文件
  • saltstack离线安装内部yum源搭建
  • mysql初次安装,设置密码
  • 将回调地狱按在地上摩擦的Promise
  • 如何恢复回收站删除的文件
  • 这是我的第一篇博客
  • 直播这么火,你知道怎么测试直播软件吗?
  • httpd
  • 游北国风光,忆南国之乡
  • IDEA 2018创建ssm工程 运行时出现500错误
  • 微软把UWP定位成业务线应用程序开发平台
  • CentOS 7使用dnf安装Memcached以及启动、停止、开机启动等设置
  • 【mysql】环境安装、服务启动、密码设置
  • javascript数组去重/查找/插入/删除
  • Java基本数据类型之Number
  • vue.js框架原理浅析
  • 分享一份非常强势的Android面试题
  • 驱动程序原理
  • 如何编写一个可升级的智能合约
  • 如何邀请好友注册您的网站(模拟百度网盘)
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 项目管理碎碎念系列之一:干系人管理
  • 一、python与pycharm的安装
  • 再谈express与koa的对比
  • gunicorn工作原理
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • 阿里云IoT边缘计算助力企业零改造实现远程运维 ...
  • 从如何停掉 Promise 链说起
  • #WEB前端(HTML属性)
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (java)关于Thread的挂起和恢复
  • (rabbitmq的高级特性)消息可靠性
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (四)模仿学习-完成后台管理页面查询
  • (一)基于IDEA的JAVA基础10
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .Net Web窗口页属性
  • .net解析传过来的xml_DOM4J解析XML文件
  • .project文件
  • @Transient注解
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [<MySQL优化总结>]
  • [2016.7 test.5] T1
  • [2018/11/18] Java数据结构(2) 简单排序 冒泡排序 选择排序 插入排序
  • [acwing周赛复盘] 第 69 场周赛20220917
  • [Android Pro] Notification的使用
  • [Android学习笔记]ScrollView的使用
  • [BZOJ2208][Jsoi2010]连通数
  • [C# WPF] 如何给控件添加边框(Border)?
  • [CVPR2021]Birds of a Feather: Capturing Avian Shape Models from Images