zoukankan      html  css  js  c++  java
  • 微信小程序登录JAVA后台

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

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

    具体的登录说明查看 小程序官方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);
            }
          }
        })
    

    效果展示

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

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

    • 发送获取用户信息请求

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

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

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

    • redis保存会话信息

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

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

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

  • 相关阅读:
    CF1462E2 Solution
    CF1450D Solution
    CF1451D Solution
    CF1442B Solution
    CF1453C Solution
    CF1455D Solution
    linux服务器部署node项目
    原生javascript实现 hasClass addClass removeClass
    图片加载完执行函数
    MySQL
  • 原文地址:https://www.cnblogs.com/demodashi/p/9436573.html
Copyright © 2011-2022 走看看