Small program flow chart of the official as follows, the official address: developers.weixin.qq.com/miniprogram… :

If this figure understanding unclear, you can also see my blog: www.cnblogs.com/ealenxie/p/…

This paper is a complete example of the implementation of micro-channel applets custom login, technology stack: SpringBoot+Shiro+JWT+JPA+Redis.

If you are interested in this example, or if you find the language verbose, check out the full project address: github.com/EalenXie/sh…

Main implementation: realized the custom login of the small program, and returned the custom login token to the small program as the login certificate. The user information is stored in the database, and the login token is cached in Redis.

The effect is as follows:

1. First call wx.login() from our applet and get the temporary credential code:

2. Simulate the use of this code, log in the small program to obtain the custom login token, and test it with Postman:

3. Invoke the interface for authentication and carry the token for authentication, and obtain the returned information:

High energy ahead, this example code more description, the following is the main construction process:

1. First, create maven project Shro-JWT-applet, POM dependencies, mainly shirO and JWT dependencies, and some basic dependencies of SpringBoot.

<?xml version="1.0" encoding="UTF-8"? >
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>name.ealen</groupId>
    <artifactId>shiro-jwt-applet</artifactId>
    <version>0.0.1 - the SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>shiro-wx-jwt</name>
    <description>Demo project for Spring Boot</description>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.1 track</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
Copy the code

2. Configure your application.yml, mainly your small programs AppId and Secret, as well as your database and Redis

Please modify the following information by yourself
spring:
  application:
    name: shiro-jwt-applet
  jpa:
    hibernate:
      ddl-auto: create      # Please modify please modify please modify

# datasource local configuration
  datasource:
    url: jdbc:mysql://localhost:3306/yourdatabase
    username: yourname
    password: yourpass
    driver-class-name: com.mysql.jdbc.Driver

# Redis local configuration please configure yourself
  redis:
    database: 0
    host: localhost
    port: 6379

# wechat applets configure appID/appSecret
wx:
  applet:
    appid: yourappid
    appsecret: yourappsecret
Copy the code

3. Define the entity information WxAccount for logging in the wechat applet that we store:

package name.ealen.domain.entity;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

/** * Created by EalenXie on 2018/11/26 10:26. ** * Created by EalenXie
@Entity
@Table
public class WxAccount {
    @Id
    @GeneratedValue
    private Integer id;
    private String wxOpenid;
    private String sessionKey;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date lastTime;
    /** * omit getter/setter */
}
Copy the code

And a simple DAO accesses the database WxAccountRepository:

package name.ealen.domain.repository;
import name.ealen.domain.entity.WxAccount;
import org.springframework.data.jpa.repository.JpaRepository;
/** * Created by EalenXie on 2018/11/26 10:32. */
public interface WxAccountRepository extends JpaRepository<WxAccount.Integer> {
    /** * Query user information based on OpenId */
    WxAccount findByWxOpenid(String wxOpenId);
}
Copy the code

4. Define our application service description WxAppletService:

package name.ealen.application;
import name.ealen.interfaces.dto.Token;
** * Created by EalenXie on 2018/11/26 10:40. ** * Created by EalenXie on 2018/11/26 10:40
public interface WxAppletService {
    /** * wechat mini program user login, the complete process can refer to the following official address, In this case, according to the process development * https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html * 1. Our wechat applet side incoming code. * 2. Call wechat code2session interface to get OpenID and session_key * 3. Custom login state (Token) * 4 according to openID and session_key. Returns a custom login state (Token) to the applet side. * 5. Our small program side calls other API that requires authentication, please carry token information in header Authorization * *@paramThe code applet side calls the code obtained by wx.login, which is used to call wechat code2session interface *@returnToken returns the backend custom login Token based on the JWT implementation */
    public Token wxUserLogin(String code);
}
Copy the code

Return to wechat applets token object declare token:

package name.ealen.interfaces.dto;
/** * Created by EalenXie on 2018/11/26 18:49. * DTO return value token object */
public class Token {
    private String token;
    public Token(String token) {
        this.token = token;
    }
    /** * omit getter/setter */
}
Copy the code

5. Configure the required basic components, RestTemplate, Redis:

package name.ealen.infrastructure.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/** * Created by EalenXie on 2018-03-23 07:37 * RestTemplate configuration class */
@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }
    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(a) {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(1000 * 60);     // Read timeout is 60 seconds
        factory.setConnectTimeout(1000 * 10);  // The connection timeout is set to 10 seconds
        returnfactory; }}Copy the code

The CacheManager configuration of Redis. This example is written as Springboot2.0 (slightly different from the 1.8 version) :

package name.ealen.infrastructure.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
/** * Created by EalenXie on 2018-03-23 07:37 * Redis config class */
@Configuration
@EnableCaching
public class RedisConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        returnRedisCacheManager.create(factory); }}Copy the code

6. JWT core filter configuration. Inherited Shiro BasicHttpAuthenticationFilter, rewrite its filtering method of authentication:

package name.ealen.infrastructure.config.jwt;
import name.ealen.domain.vo.JwtToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/ * * * Created by EalenXie on 2018/11/26. According to core Filter configuration * * JWT all requests through the Filter, so we inherit official BasicHttpAuthenticationFilter, And override the authentication method. * Executables preHandle->isAccessAllowed->isLoginAttempt->executeLogin */
public class JwtFilter extends BasicHttpAuthenticationFilter {
    /** * Check whether the user wants to perform the required operation * check whether the header contains the Authorization field */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        String auth = getAuthzHeader(request);
        returnauth ! =null && !auth.equals("");
    }
    /** * This method calls login to validate the logic */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (isLoginAttempt(request, response)) {
            JwtToken token = new JwtToken(getAuthzHeader(request));
            getSubject(request, response).login(token);
        }
        return true;
    }
    /** * provides cross-domain support */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods"."GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // An option request is first sent across domains
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response); }}Copy the code

Core configuration of JWT (including Token encryption creation, JWT renewal, decryption verification) :

package name.ealen.infrastructure.config.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import name.ealen.domain.entity.WxAccount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/** * Created by EalenXie on 2018/11/22 17:16. */
@Component
public class JwtConfig {
    /** * JWT custom key */
    private static final String SECRET_KEY = "5371f568a45e5ab1f442c38e0932aef24447139b";
    /** * JWT expiration time = 7200 seconds, that is, two hours */
    private static long expire_time = 7200;
    @Autowired
    private StringRedisTemplate redisTemplate;
    /** * Create token based on wechat user login information * note: the token will be cached in Redis for secondary verification ** The cache time in Redis should be the same as the expiration time of JWT token@paramWxAccount wechat user information *@returnReturn JWT token */
    public String createTokenByWxAccount(WxAccount wxAccount) {
        String jwtId = UUID.randomUUID().toString();                 //JWT random ID as the authentication key
        //1. The encryption algorithm performs signature to obtain the token
        Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
        String token = JWT.create()
                .withClaim("wxOpenId", wxAccount.getWxOpenid())
                .withClaim("sessionKey", wxAccount.getSessionKey())
                .withClaim("jwt-id", jwtId)
                .withExpiresAt(new Date(System.currentTimeMillis() + expire_time*1000))  //JWT configures the correct posture for expiration time
                .sign(algorithm);
        //2. Redis cache JWT, note: Please be consistent with JWT expiration time
        redisTemplate.opsForValue().set("JWT-SESSION-" + jwtId, token, expire_time, TimeUnit.SECONDS);
        return token;
    }
    /** * Check whether the token is correct * 1. Decrypt the jwt-id based on the token, and check whether redisToken matches with redis * 2. RedisToken is then decrypted. If the decryption succeeds, the process continues and the token is renewed * *@paramToken key *@returnReturns whether the verification passes */
    public boolean verifyToken(String token) {
        try {
            //1. Decrypt jwt-id according to token, and check whether redisToken match is the same
            String redisToken = redisTemplate.opsForValue().get("JWT-SESSION-" + getJwtIdByToken(token));
            if(! redisToken.equals(token))return false;
            //2. Get JWTVerifier with same algorithm
            Algorithm algorithm = Algorithm.HMAC256(SECRET_KEY);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("wxOpenId", getWxOpenIdByToken(redisToken))
                    .withClaim("sessionKey", getSessionKeyByToken(redisToken))
                    .withClaim("jwt-id", getJwtIdByToken(redisToken))
                    .acceptExpiresAt(System.currentTimeMillis() + expire_time*1000 )  //JWT correctly configures the renewal pose
                    .build();
            //3. Verify the token
            verifier.verify(redisToken);
            //4. Redis cache JWT renewal
            redisTemplate.opsForValue().set("JWT-SESSION-" + getJwtIdByToken(token), redisToken, expire_time, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) { // Any exception caught is considered a validation failure
            return false; }}/** * Obtain wxOpenId based on Token (note: it is possible to decrypt wxOpenId even if Token is incorrect) */
    public String getWxOpenIdByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim("wxOpenId").asString();
    }
    /** * Obtain sessionKey */ based on Token
    public String getSessionKeyByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim("sessionKey").asString();
    }
    /** * Get jwt-id */ based on Token
    private String getJwtIdByToken(String token) throws JWTDecodeException {
        return JWT.decode(token).getClaim("jwt-id").asString(); }}Copy the code

7. Customize Shiro’s Realm configuration, which is the logical configuration for custom login and authorization:

package name.ealen.infrastructure.config.shiro;
import name.ealen.domain.vo.JwtToken;
import name.ealen.infrastructure.config.jwt.JwtConfig;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/** * Created by EalenXie on 2018/11/26 12:12. * A configuration management class allRealm() method gets all realms */
@Component
public class ShiroRealmConfig {
    @Resource
    private JwtConfig jwtConfig;
    /** * Configure all custom realms for convenience when there may be more than one realm */
    public List<Realm> allRealm(a) {
        List<Realm> realmList = new LinkedList<>();
        AuthorizingRealm jwtRealm = jwtRealm();
        realmList.add(jwtRealm);
        return Collections.unmodifiableList(realmList);
    }
    /** * Customizes the Realm of JWT * Overriding the supports() method of Realm is key to login judgment through JWT */
    private AuthorizingRealm jwtRealm(a) {
        AuthorizingRealm jwtRealm = new AuthorizingRealm() {
            /** * Note the pitfall: this method must be overridden, otherwise Shiro will report an error * Because the JWTToken was created to replace Shiro's native token, it must be replaced explicitly in this method, otherwise the judgment will always fail */
            @Override
            public boolean supports(AuthenticationToken token) {
                return token instanceof JwtToken;
            }
            @Override
            protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
                return new SimpleAuthorizationInfo();
            }
            /** * Verify the token logic */
            @Override
            protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
                String jwtToken = (String) token.getCredentials();
                String wxOpenId = jwtConfig.getWxOpenIdByToken(jwtToken);
                String sessionKey = jwtConfig.getSessionKeyByToken(jwtToken);
                if (wxOpenId == null || wxOpenId.equals(""))
                    throw new AuthenticationException("user account not exits , please check your token");
                if (sessionKey == null || sessionKey.equals(""))
                    throw new AuthenticationException("sessionKey is invalid , please check your token");
                if(! jwtConfig.verifyToken(jwtToken))throw new AuthenticationException("token is invalid , please check your token");
                return newSimpleAuthenticationInfo(token, token, getName()); }}; jwtRealm.setCredentialsMatcher(credentialsMatcher());return jwtRealm;
    }
    /** ** note the pit: password checksum, here is JWT form, do not need password checksum encryption, directly return true(if not set, the default value is false, that is, always failed authentication) */
    private CredentialsMatcher credentialsMatcher(a) {
        return (token, info) -> true; }}Copy the code

Shiro’s core configuration, which includes configuring Realm:

package name.ealen.infrastructure.config.shiro;
import name.ealen.infrastructure.config.jwt.JwtFilter;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;
/** * Created by EalenXie on 2018/11/22 18:28. */
@Configuration
public class ShirConfig {
    /** * SecurityManager, the SecurityManager with which all security-related operations interact; * it manages all the Subject, all Subject to bind to the SecurityManager, interactions with all of the Subject will be delegated to the SecurityManager * DefaultWebSecurityManager: Creates a default DefaultSubjectDAO (it will default to create DefaultSessionStorageEvaluator) * will create DefaultWebSubjectFactory * by default Will default to create ModularRealmAuthenticator * /
    @Bean
    public DefaultWebSecurityManager securityManager(ShiroRealmConfig shiroRealmConfig) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealms(shiroRealmConfig.allRealm());     / / set the realm
        DefaultSubjectDAO subjectDAO = (DefaultSubjectDAO) securityManager.getSubjectDAO();
        // Disable the built-in session
        DefaultSessionStorageEvaluator evaluator = (DefaultSessionStorageEvaluator) subjectDAO.getSessionStorageEvaluator();
        evaluator.setSessionStorageEnabled(Boolean.FALSE);
        subjectDAO.setSessionStorageEvaluator(evaluator);
        return securityManager;
    }
    /** * Configure Shiro's access policy */
    @Bean
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt".new JwtFilter());
        factoryBean.setFilters(filterMap);
        factoryBean.setSecurityManager(securityManager);
        Map<String, String> filterRuleMap = new HashMap<>();
        // Login-related apis do not need to be blocked by filters
        filterRuleMap.put("/api/wx/user/login/**"."anon");
        filterRuleMap.put("/api/response/**"."anon");
        // All requests go through JWT Filter
        filterRuleMap.put("/ * *"."jwt");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }
    /** * Add annotation support */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(a) {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); // Enforce the use of cglib to prevent duplicate proxies and problems that can cause proxy errors
        return defaultAdvisorAutoProxyCreator;
    }
    /** * add annotation dependency */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(a) {
        return new LifecycleBeanPostProcessor();
    }
    /** * Enable annotation validation */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        returnauthorizationAttributeSourceAdvisor; }}Copy the code

JwtToken object for Shiro authentication:

package name.ealen.domain.vo;
import org.apache.shiro.authc.AuthenticationToken;
** * Created by EalenXie on 2018/11/22 18:21. ** Created by EalenXie on 2018/11/22 18:21
public class JwtToken implements AuthenticationToken {
    private String token;
    public JwtToken(String token) {
        this.token = token;
    }
    @Override
    public Object getPrincipal(a) {
        return token;
    }
    @Override
    public Object getCredentials(a) {
        return token;
    }
    public String getToken(a) {
        return token;
    }
    public void setToken(String token) {
        this.token = token; }}Copy the code

8. Realize the behavior and business logic of the entity. In this case, call wechat interface Code2Session and create return token:

package name.ealen.domain.service;
import name.ealen.application.WxAppletService;
import name.ealen.domain.entity.WxAccount;
import name.ealen.domain.repository.WxAccountRepository;
import name.ealen.domain.vo.Code2SessionResponse;
import name.ealen.infrastructure.config.jwt.JwtConfig;
import name.ealen.infrastructure.util.HttpUtil;
import name.ealen.infrastructure.util.JSONUtil;
import name.ealen.interfaces.dto.Token;
import org.apache.shiro.authc.AuthenticationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.Date;
/** * Created by EalenXie on 2018/11/26 10:50
@Service
public class WxAccountService implements WxAppletService {
    @Resource
    private RestTemplate restTemplate;
    @Value("${wx.applet.appid}")
    private String appid;
    @Value("${wx.applet.appsecret}")
    private String appSecret;
    @Resource
    private WxAccountRepository wxAccountRepository;
    @Resource
    private JwtConfig jwtConfig;
    /** * code2Session interface of wechat to obtain wechat user information https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html */
    private String code2Session(String jsCode) {
        String code2SessionUrl = "https://api.weixin.qq.com/sns/jscode2session";
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("appid", appid);
        params.add("secret", appSecret);
        params.add("js_code", jsCode);
        params.add("grant_type"."authorization_code");
        URI code2Session = HttpUtil.getURIwithParams(code2SessionUrl, params);
        return restTemplate.exchange(code2Session, HttpMethod.GET, new HttpEntity<String>(new HttpHeaders()), String.class).getBody();
    }
    /** * wechat mini program user login, the complete process can refer to the following official address, In this case, according to the process development * https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html *@paramThe code applet side calls the code obtained by wx.login, which is used to call wechat code2session interface *@returnReturn backend custom logon token based JWT implementation */
    @Override
    public Token wxUserLogin(String code) {
        // 1. code2session returns JSON data
        String resultJson = code2Session(code);
        //2. Parse data
        Code2SessionResponse response = JSONUtil.jsonString2Object(resultJson, Code2SessionResponse.class);
        if(! response.getErrcode().equals("0"))
            throw new AuthenticationException("Code2session failed:" + response.getErrmsg());
        else {
            //3. Check whether the user exists in the local database
            WxAccount wxAccount = wxAccountRepository.findByWxOpenid(response.getOpenid());
            if (wxAccount == null) {
                wxAccount = new WxAccount();
                wxAccount.setWxOpenid(response.getOpenid());    // Create a user if it does not exist
            }
            //4. Update sessionKey and login time
            wxAccount.setSessionKey(response.getSession_key());
            wxAccount.setLastTime(new Date());
            wxAccountRepository.save(wxAccount);
            // 5. JWT returns a custom login Token
            String token = jwtConfig.createTokenByWxAccount(wxAccount);
            return newToken(token); }}}Copy the code

Code2SessionResponse Code2SessionResponse:

package name.ealen.domain.vo;
/** * Code2Session interface return value object * https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/code2Session.html */
public class Code2SessionResponse {
    private String openid;
    private String session_key;
    private String unionid;
    private String errcode = "0";
    private String errmsg;
    private int expires_in;
    /** * omit getter/setter */
}
Copy the code

9. Define our interface information WxAppletController. This example contains an API for logging in to obtain token and a test API for authentication:

package name.ealen.interfaces.facade;
import name.ealen.application.WxAppletService;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/** * Created by EalenXie on 2018/11/26 10:44. ** * Created by EalenXie on 2018/11/26 10:44
@RestController
public class WxAppletController {
    @Resource
    private WxAppletService wxAppletService;
    /** * wechat applets user login API * returns the applets custom login token */
    @PostMapping("/api/wx/user/login")
    public ResponseEntity wxAppletLoginApi(@RequestBody Map<String, String> request) {
        if(! request.containsKey("code") || request.get("code") = =null || request.get("code").equals("")) {
            Map<String, String> result = new HashMap<>();
            result.put("msg"."Missing parameter code or code is invalid");
            return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
        } else {
            return new ResponseEntity<>(wxAppletService.wxUserLogin(request.get("code")), HttpStatus.OK); }}/** * The test interface needs to be authenticated@RequiresAuthenticationTo invoke this interface, the header must carry the custom login authorization */
    @RequiresAuthentication
    @PostMapping("/sayHello")
    public ResponseEntity sayHello(a) {
        Map<String, String> result = new HashMap<>();
        result.put("words"."hello World");
        return newResponseEntity<>(result, HttpStatus.OK); }}Copy the code

10. Run the main class, check the connection with the database and Redis, test:

package name.ealen;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/** * Created by EalenXie on 2018/11/26 10:25. */
@SpringBootApplication
public class ShiroJwtAppletApplication {
    public static void main(String[] args) { SpringApplication.run(ShiroJwtAppletApplication.class, args); }}Copy the code

The above is the logical process description and realization of the complete example of wechat applet login based on Shiro and JWT. Original is not easy, reproduced please indicate the source, thank you very much for your support and comments.