One, foreword

Had planned for this week serious write a article, learning the relevant practice course, but because of the overtime, disrupted the meeting plan, combed the some idea today, write a WeChat small application log in actual combat, every day a lot of things, can only try to find time to learn some more content, I plan to speak of this article is divided into two parts:

Updated on 21 April 2021

  • At present, I will write the content of small program login first, and I will finish the registration tomorrow. There are still some knowledge points that need to be explained in detail, and some content will be added. Please forgive me! (April 20, 2021)

  • Today, I will finish the registration of the small program and finally write a practical article. Later, I will learn some content about the development of permissions system, and then share with you. Maybe it is not rigorous enough, I will strengthen my study and keep writing good articles (April 21, 2021).

  • Wechat small program login – server

    • The user login
    • User registration
  • Wechat mini program login – client

    • Access code
    • User registration
    • The user login

Today we only talk about server-side development, the main techniques are as follows:

  • Springboot
  • redis
  • shiro
  • jwt

1.1 What is Shiro?

certification

For the system, we are familiar with every time we enter the background will pop up the login box, including verification code login, SMS login, fingerprint login and so on, this is a authentication operation

When I was young, when I was alone at home, my parents would always tell me, “Stay at home. If strangers knock on the door, you are not allowed to knock.”

Me: “What is a stranger?”

Parents: “Just people you don’t know”

The conversation above, full of memories, can also be interpreted as validation

First, I need to get to know you, and then I can open the door for you

In the system, I can distinguish whether I know you or not by the following conditions: whether you have the correct account and password, if so, I know you (successfully authenticated), if not, then I do not know you (rejected request).

That’s what we call certification

authorization

I remember when I was in primary school, pocket money was 5 jiao in the morning, 5 jiao in the afternoon, my mother would put the money in the drawer inside the room, every time to get pocket money, my mother let me go to the drawer to take 50 jiao, but the premise is that my mother allows me to take it myself

Mother gave me permission to take 50 cents from the drawer. In other words, mother gave me permission to take the money from the drawer

There are two important things here, who authorizes it? What can be done after authorization?

Another example is: my mother authorized me to go to the drawer to take money, but I did not take money from the inside of the home drawer, but to the next aunt’s drawer to take money, so this is stealing

So authorization has scope, and if the user exceeds the scope of authorization that I specified, then the system will have all kinds of problems

That’s the authorization

What is Shiro?

The above two simple examples may not be rigorous, but you can get a sense of the core of the permissions system: authentication and authorization

So what exactly is Shiro?

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. 
Copy the code

Shiro is a powerful and easy-to-use Java security framework that we call the following four building blocks of Shiro:

  • certification
  • authorization
  • Session management
  • encryption

In light of the above example, let’s rethink an example:

With the development of technology, face recognition and fingerprint lock are installed in my home. At this time, I don’t need to lie on the cat’s eye to identify everyone as I did when I was a child. Now, facial recognition and fingerprint lock and other devices replace me to perform authentication operations, and I just lie on the sofa drinking drinks and eating snacks

With Shiro, we don’t need to write more filters and Filter functions. Shiro already provides a variety of filters by default. If you don’t want to use the default Filter, you can customize it

Since this article is not about Shiro, I will write more about Shiro later

1.2 What is Jwt?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way to securely transfer information between parties as JSON objects. Because this information is digitally signed, it can be authenticated and trusted. JWT can be signed using secret (using the HMAC algorithm) or using RSA or ECDSA’s public/private key pair.

Composition of JWT:

  • The header
  • The payload
  • The signature
xxxxx.yyyyy.zzzzz
Copy the code

1, the headers

The header usually consists of two parts: the type of token (that is, JWT) and the signature algorithm used, such as HMAC SHA256 or RSA.

{
  "alg": "HS256"."typ": "JWT"
}
Copy the code

2. Payload

The load here is mainly to store some non-privacy (password, ID number, telephone number) content, generally we mainly store user ID, account number and so on

{
  "sub": "1234567890"."name": "John Doe"."admin": true
}
Copy the code

3, signature

If you want to use the HMAC SHA256 algorithm, the signature is created as follows:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
Copy the code

The final JWT generated is:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJ SMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cCopy the code

2. Basic environment construction

Let’s start with the formal content. First, we need to build our base environment

  • Introduction of depend on
  • The configuration file
  • Jwt tools
  • Knife4j configuration
  • Generic response class
  • Custom exception
  • Verify back-end parameters
  • Exception handling class
|____src | |____main | | |____resources | | | |____mappers | | | | |____UserMapper.xml | | | |____application.yml | | |____java | | | |____com | | | | |____yangzinan | | | | | |____goflywx | | | | | | |____security | | | | | | | |____token | | | | | | | | |____JwtToken.java | | | | | | | |____config | | | | | | | | |____ShiroConfig.java | | | | | | | |____filter | | | | | | | | |____JwtFilter.java | | | | | | | |____realm | | | | | | | | |____UserRealm.java | | | |  | | |____entity | | | | | | | |____User.java | | | | | | | |____LoginParam.java | | | | | | |____GoflyWxApplication.java | | | | | | |____mapper | | | | | | | |____UserMapper.java | | | | | | |____utils | | | | |  | | |____JwtUtil.java | | | | | | |____controller | | | | | | | |____PubController.java | | | | | | | |____TestController.java | | | | | | |____common | | | | | | | |____config | | | | | | | | |____Knife4jConfiguration.java | | | | | | | |____exception | | | | | | | | |____GoflyException.java | | | | | | | | |____GoflyExceptionHandler.java | | | | | | | |____respones | | | | | | | | |____GoflyRespones.java | | | | | | |____service | | | | | | | |____impl | | | | | | | | |____UserServiceImpl.java | | | | | | | |____IUserService.javaCopy the code

2.1 Importing Dependencies

Shiro and others need to be configured to use it. This is a bit different from Spring Security, where you can configure almost nothing and only need to introduce dependencies to implement user authentication operations

        <! -- Spring dependencies -->
        <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>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <! - mybatis dependence - >
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <! - mysql driver - >
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <! - lombok dependence - >
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <! -- Database connection pool dependency -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

        <! -- Status code -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.4.13</version>
        </dependency>

        <! --knife4j-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>Mid-atlantic moved</version>
        </dependency>

        <! -- Backend validation -->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.1.5. The Final</version>
        </dependency>

        <!--hutool工具类-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.4.0</version>
        </dependency>

        <! --shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.5.3</version>
        </dependency>

        <! --jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>

        <! Read the yML configuration file -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <! -- Tools -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.11</version>
        </dependency>

        <! - aop support - >
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
Copy the code

2.2 Configuration File

Configuration file Main configuration:

  • mysql
  • mybatis
  • WeChat parameters
  • JWT parameters

Updated on 21 April 2021

CREATE TABLE 't_user' (' id 'int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键', 'open_id' varchar(200) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT ' 'nickname' varchar(200) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'nickname ', Photo 'varchar(200) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT ', 'name' varchar(20) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'name ', 'sex' enum(' male ',' female ') CHARACTER SET UTF8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'gender ', 'tel' char(11) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT ' 'email' varchar(200) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT 'email' varchar(200) CHARACTER SET utf8 COLLATE UTf8_general_ci NULL DEFAULT NULL COMMENT ' 'hiredate' date NULL DEFAULT NULL COMMENT 'hiredate ',' role 'json NOT NULL COMMENT' role ', 'root' tinyint(1) NULL COMMENT 'whether ',' dept_id 'int(10) UNSIGNED NULL DEFAULT NULL COMMENT' Department id ', 'status' tinyint(4) NULL COMMENT' status ', 'create_time' datetime(0) NULL ON UPDATE CURRENT_TIMESTAMP(0) COMMENT 'create time ', PRIMARY KEY (`id`) USING BTREE, UNIQUE INDEX `unq_open_id`(`open_id`) USING BTREE, INDEX `unq_email`(`email`) USING BTREE, INDEX `idx_dept_id`(`dept_id`) USING BTREE, INDEX `idx_status`(`status`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = Utf8_general_ci COMMENT = 'user table' ROW_FORMAT = DYNAMIC;Copy the code
server:
  port: 9000
  servlet:
    context-path: /gofly-wx

# app name
spring:
  application:
    name: gofly-wx
  # druid configuration
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      url: JDBC: mysql: / / 192.168.2.128:3306 / goflywx? useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
      username: root
      password: yang19960127
      initial-size: 8
      max-active: 16
      min-idle: 8
      max-wait: 60000
      test-while-idle: true
      test-on-return: false
      test-on-borrow: false

# mybatis configuration
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mappers/*.xml

# Custom configuration
gofly:
 # wechat configuration
  wx:
    #appid
    appid: * * * * * * * * * * * * * * * * * * *
    #app-secret
    app-secret: * * * * * * * * * * * * * * * * * * *
    # wechat server address
    url: * * * * * * * * * * * * * * * * * * *
  jwt:
    # key
    secret: mic123
    Token expiration time is 5 days
    expire: 5
    # Token cache expiration time is 10 days
    cache-expire: 10
Copy the code

2.3 JWT Tool classes

When we were doing small program development, it was a separate project from the front and back ends, so we chose to use JWT. The advantage of this is that it can be used by both wechat small program and Android client.

/** * JWT utility class */
@Slf4j
@Component
public class JwtUtil {

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

    @Value("${gofly.jwt.expire}")
    private Integer expire;

    /** * Create token *@paramUserId userId *@return* /
    public String createToken(int userId){
        // Create an expiration date
        Date date = DateUtil.offset(new Date(), DateField.DAY_OF_YEAR,expire).toJdkDate();
        // Encryption algorithm
        Algorithm algorithm = Algorithm.HMAC256(secret);
        JWTCreator.Builder builder = JWT.create();
        String token = builder.withClaim("userId", userId).withExpiresAt(date).sign(algorithm);
        return token;
    }


    /** * Get user id *@param token
     * @return* /
    public int getUserId(String token) throws GoflyException {
        try{
            DecodedJWT decode = JWT.decode(token);
            return decode.getClaim("userId").asInt();
        }catch (Exception e){
            // Custom exception
            throw new GoflyException("Token invalid"); }}/** * Check whether the token is valid *@param token
     */
    public void verifierToken(String token){ Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm).build(); verifier.verify(token); }}Copy the code

2.4 Knife4j configuration

/** * knife4j configuration file */
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfiguration {

    @Bean(value = "defaultApi2")
    public Docket defaultApi2(a) {
        Docket docket=new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        //.title("swagger-bootstrap-ui-demo RESTful APIs")
                        .description("Gofly Permission System API documentation")
                        .contact(new Contact(null.null."[email protected]"))
                        .version("1.0")
                        .build())
                // Group name
                .groupName("1.0")
                .select()
                // Specify the Controller scan package path
                .apis(RequestHandlerSelectors.basePackage("com.yangzinan.goflywx.controller"))
                .paths(PathSelectors.any())
                .build();
        returndocket; }}Copy the code

Access interface article

http://localhost:9000/gofly-wx/doc.html
Copy the code

2.6 Generic Response Objects

/** * Generic response class */
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class GoflyRespones<T>  {

    private String message;
    private Integer code;
    private T data;

    public static GoflyRespones success(String message){
        GoflyRespones r = new GoflyRespones();
        r.setMessage(message);
        r.setCode(HttpStatus.HTTP_OK);
        return r;
    }

    public static GoflyRespones success(a){
        GoflyRespones r = new GoflyRespones();
        r.setMessage("Success");
        r.setCode(HttpStatus.HTTP_OK);
        return r;
    }

    public static GoflyRespones error(String message){
        GoflyRespones r = new GoflyRespones();
        r.setMessage(message);
        r.setCode(HttpStatus.HTTP_INTERNAL_ERROR);
        return r;
    }

    public static GoflyRespones error(a){
        GoflyRespones r = new GoflyRespones();
        r.setMessage("System exception, please contact administrator.");
        r.setCode(HttpStatus.HTTP_INTERNAL_ERROR);
        return r;
    }

    public static GoflyRespones error(Integer code,String message){
        GoflyRespones r = new GoflyRespones();
        r.setMessage(message);
        r.setCode(code);
        return r;
    }


    public GoflyRespones putData(T data){
        this.setData(data);
        return this; }}Copy the code

2.7 Custom Exceptions

/** * Custom exception */
public class GoflyException extends RuntimeException{
    public GoflyException(String message){
        super(message); }}Copy the code

2.8 Exception Handling Classes

/** * Generic response class */
@RestControllerAdvice
public class GoflyExceptionHandler {


    /** * Custom exception handling *@param e
     * @return* /
    @ExceptionHandler(value = GoflyException.class)
    public GoflyRespones goflyExceptionHandler(GoflyException e){
        return GoflyRespones.error(e.getMessage());
    }


    /** *@param e
     * @return* /
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public GoflyRespones methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e){
        BindingResult bindingResult = e.getBindingResult();
        List<String> errorMsg = new ArrayList<>();
        if(bindingResult.hasErrors()){
            for(ObjectError error:bindingResult.getAllErrors()){ errorMsg.add(error.getDefaultMessage()); }}return GoflyRespones.error("Parameter verification failed").putData(errorMsg);
    }

    /** * SQL exception handling *@param e
     * @return* /
    @ExceptionHandler(value = SQLSyntaxErrorException.class)
    public GoflyRespones sqlException(SQLSyntaxErrorException e){
        return GoflyRespones.error("SQL exceptions").putData(e.getMessage()); }}Copy the code

Shiro certification and authorization

3.1 User-defined Token

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) {
        returntoken; }}Copy the code

3.2 Customize a Realm

Query user information 【 usermapper.xml 】

  <! -- Get user information from user ID -->
  <select id="selectUserByUserId" resultType="com.yangzinan.goflywx.entity.User">
    select
      <include refid="Base_Column_List"></include>
    from t_user
    where id = #{userId}
  </select>
Copy the code

Query user information [usermapper.java]

@Mapper
public interface UserMapper {
    public User selectUserByUserId(Integer userId);
}
Copy the code

Query user information [iuserservice.java]

/** * user interface */
public interface IUserService {
    public Integer login(String code);
    public User selectUserByUserId(Integer userId);
}
Copy the code

Query user information userServicePl.java

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private IUserService userService;

    /** * Query user information by user ID *@param userId
     * @return* /
    @Override
    public User selectUserByUserId(Integer userId) {
        returnuserMapper.selectUserByUserId(userId); }}Copy the code

Custom realm

/** * Custom realm */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private IUserService userService;

    @Autowired
    private JwtUtil jwtUtil;
  
   /** * this is mandatory, if not an error *@param token
     * @return* /
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    /** * authorized *@param principalCollection
     * @return* /
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //TODO:The authorization function is not implemented temporarily
        return null;
    }

    /** * Authentication *@param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        String token = (String) authenticationToken.getPrincipal();
        int userId = jwtUtil.getUserId(token);
        User user = userService.selectUserByUserId(userId);
        if(user == null) {throw new GoflyException("User is locked. Please contact administrator.");
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,token,getName());
        returninfo; }}Copy the code

3.3 Customizing a Filter

@Component
public class JwtFilter extends AuthenticatingFilter {

    @Autowired
    private JwtUtil jwtUtil;

    /** * Create token *@param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;

        / / access token
        String token = getToken(request);
        if(StringUtils.isBlank(token)){
            return null;
        }
        // If the token exists, assemble the token into a custom JwtToken
        return new JwtToken(token);
    }

    /** * Determine whether the request needs to be processed by Shiro *@param servletRequest
     * @param servletResponse
     * @param mappedValue
     * @return* /
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        // If it is Ajax, the option request will be sent first
        if(request.getMethod() == Method.OPTIONS.name()){
            return true;
        }
        // All other requests are directed to Shiro
        return false;
    }

    /** * handles requests that shiro needs to handle, that is, isAccessAllowed returns false *@param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // Check whether the token exists
        String token = getToken(request);
        if(StringUtils.isBlank(token)){
            result(response,request,HttpStatus.HTTP_UNAUTHORIZED,"Token does not exist");
            return false;
        }
        // Verify the token is valid
        try{
            jwtUtil.verifierToken(token);
            // The token expires
        }catch (TokenExpiredException e){
            result(response,request,HttpStatus.HTTP_UNAUTHORIZED,"Token expired");
            // The token is invalid
        }catch (JWTDecodeException e){
            result(response,request,HttpStatus.HTTP_UNAUTHORIZED,"Token invalid");
        }
        // Perform the operation
        boolean b = executeLogin(request, response);
        return b;
    }



    @Override
    public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
        super.doFilterInternal(request, response, chain);
    }

    /** * get token * TODO: If the token is present in the request header, return it directly, if not, get * from the request parameters@param request
     * @return* /
    private String getToken(HttpServletRequest request){
        String token = request.getHeader("token");
        if(StringUtils.isBlank(token)){
            token = request.getParameter("token");
        }
        return token;
    }

    /** * Response message *@param response
     * @param request
     * @param code
     * @param message
     * @throws IOException
     */
    private void result(HttpServletResponse response,HttpServletRequest request,Integer code,String message) throws IOException {
        response.setHeader("Content-Type"."application/json; charset=UTF-8");
        // Cross-domain support
        response.setHeader("Access-Control-Allow-Credentials"."true");
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); GoflyRespones error = GoflyRespones.error(code, message); response.getWriter().print(JSONUtil.parse(error).toStringPretty()); }}Copy the code

3.4 ShiroConfig

/** * shiro configuration file */
@Configuration
public class ShiroConfig {

    /** * Security manager *@param userRealm
     * @return* /
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        securityManager.setRememberMeManager(null);
        // Close the sessionDao and session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
        / / injection subjecDao
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }

    @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager,JwtFilter jwtFilter){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // Custom filters
        Map<String, Filter> filterMap = new HashMap<>();
        filterMap.put("jwt",jwtFilter);
        shiroFilterFactoryBean.setFilters(filterMap);

        // Called when the user is not logged in
        shiroFilterFactoryBean.setLoginUrl("/pub/login");

        // To ensure the order of execution, use LinkedHashMap
        Map<String,String> urlFilter = new LinkedHashMap<>();
        //kinfe4j related path
        urlFilter.put("/doc.html"."anon");
        urlFilter.put("/webjars/**"."anon");
        urlFilter.put("/swagger-resources/**"."anon");
        urlFilter.put("/v2/**"."anon");


        urlFilter.put("/pub/**"."anon");
        urlFilter.put("/favicon.ico"."anon");
        urlFilter.put("/druid/**"."anon");
        urlFilter.put("/ * *"."jwt");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(urlFilter);
        return shiroFilterFactoryBean;

    }

    /** * lifecycle management *@return* /
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(a){
        return new LifecycleBeanPostProcessor();
    }

    /** ** Annotation support *@param securityManager
     * @return* /
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }


    /** * Because the custom Filter, if the bean below is not configured, will be started when the application is started, which will cause anon and other invalid * TODO: we will talk about this later *@param jwtFilter
     * @return* /
    @Bean
    public FilterRegistrationBean registrationBean(JwtFilter jwtFilter){
        FilterRegistrationBean regist = new FilterRegistrationBean();
        regist.setFilter(jwtFilter);
        regist.setEnabled(false);
        returnregist; }}Copy the code

Iv. Specific Registration process (Updated on April 21, 2021)

Before, I could not finish this article, there is still the content of registration, if you read the content of login, in fact, registration is relatively simple, the basic idea:

  • Uni.login Gets the temporary authorization string
  • After successful acquisition, use uni.getUserInfo to obtain wechat user information
  • Request the temporary authorization string code, along with user information, to the back end
  • Verify parameters on the backend
  • After passing the verification, use code to exchange for openId, and then save it to the database together with the user information
  • If the verification fails, an exception is thrown

4.1 Small program to obtain wechat user information

According to the following code, increase understanding!

Get code and get user information

<template>
	<view>
		<button class="login-btn" open-type="getUserInfo" @tap="register()">registered</button>
	</view>
</template>

<script>
	export default {
		data() {
			return {
        // Store user information
				user:"".// Stores temporary authorization strings
				code:""}},methods: {
      let that = this;
			// Register operation
			register:function(){
        // Get the temporary authorization string
				uni.login({
					provider:"weixin".success:function(resp){
            // Save the temporary authorization string
						that.code = resp.code;
            // Get user information
						uni.getUserInfo({
							provider:"weixin".success:function(resp){
                // Save the user information
								that.user = resp.userInfo;
                
                	// The user information is sent to the backend
								uni.request({
									url:"http://localhost:9000/gofly-wx/pub/register".method:"POST".data: {code: that.code,
										nickname: that.user.nickName,
										avatarUrl: that.user.avatarUrl
									},
									success: (resp) = > {
										console.log(resp); }})}})}})}}}</script>
Copy the code

4.2 Saving User Information on the Backend

Create request parameter entities

/** * Registration request information */
@Data
@ToString
public class RegisterForm {

    @notblank (message = "Temporary authorization string cannot be empty ")
    private String code;

    @notblank (message = "nickname cannot be empty ")
    private String nickname;

    @notblank (message = "head cannot be empty ")
    private String avatarUrl;

}
Copy the code

The Mapper layer

@Mapper
public interface UserMapper {
    int insertSelective(User record);
}
Copy the code
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.yangzinan.goflywx.entity.User" useGeneratedKeys="true">
  insert into t_user
  <trim prefix="(" suffix=")" suffixOverrides=",">
    <if test="openId ! = null">
      open_id,
    </if>
    <if test="nickname ! = null">
      nickname,
    </if>
    <if test="photo ! = null">
      photo,
    </if>
    <if test="name ! = null">
      `name`,
    </if>
    <if test="sex ! = null">
      sex,
    </if>
    <if test="tel ! = null">
      tel,
    </if>
    <if test="email ! = null">
      email,
    </if>
    <if test="hiredate ! = null">
      hiredate,
    </if>
    <if test="role ! = null">
      `role`,
    </if>
    <if test="root ! = null">
      root,
    </if>
    <if test="deptId ! = null">
      dept_id,
    </if>
    <if test="status ! = null">
      `status`,
    </if>
    <if test="createTime ! = null">
      create_time,
    </if>
  </trim>
  <trim prefix="values (" suffix=")" suffixOverrides=",">
    <if test="openId ! = null">
      #{openId,jdbcType=VARCHAR},
    </if>
    <if test="nickname ! = null">
      #{nickname,jdbcType=VARCHAR},
    </if>
    <if test="photo ! = null">
      #{photo,jdbcType=VARCHAR},
    </if>
    <if test="name ! = null">
      #{name,jdbcType=VARCHAR},
    </if>
    <if test="sex ! = null">
      #{sex,jdbcType=OTHER},
    </if>
    <if test="tel ! = null">
      #{tel,jdbcType=CHAR},
    </if>
    <if test="email ! = null">
      #{email,jdbcType=VARCHAR},
    </if>
    <if test="hiredate ! = null">
      #{hiredate,jdbcType=DATE},
    </if>
    <if test="role ! = null">
      #{role,jdbcType=OTHER},
    </if>
    <if test="root ! = null">
      #{root,jdbcType=BOOLEAN},
    </if>
    <if test="deptId ! = null">
      #{deptId,jdbcType=INTEGER},
    </if>
    <if test="status ! = null">
      #{status,jdbcType=TINYINT},
    </if>
    <if test="createTime ! = null">
      #{createTime,jdbcType=TIMESTAMP},
    </if>
  </trim>
</insert>
Copy the code

The Service layer

public interface IRegisterService {

    public Integer save(RegisterForm form);

}
Copy the code
@Service
public class RegisterServiceImpl implements IRegisterService {

    @Autowired
    private UserMapper userMapper;

    @Value("${gofly.wx.appid}")
    private String appId;

    @Value("${gofly.wx.app-secret}")
    private String appSecret;

    @Value("${gofly.wx.url}")
    private String url;

    /** * Save user information *@param form
     * @return* /
    @Transactional
    @Override
    public Integer save(RegisterForm form) {
        User user = new User();
        try{
            String openId = this.getOpenId(form.getCode());
            user.setNickname(form.getNickname());
            user.setOpenId(openId);
            user.setRole("[0].");
            user.setDeptId(1);
            user.setStatus(Byte.parseByte("1"));
            user.setRoot(true);

            // Save the user
            int count = userMapper.insertSelective(user);

            return count;
        }catch (Exception e){
            throw new GoflyException("Failed to save user information"); }}/** * Exchange openId * from wechat server with code@param code
     * @return* /
    private String getOpenId(String code){
        HashMap params = new HashMap();
        params.put("appid",appId);
        params.put("secret",appSecret);
        params.put("js_code",code);
        params.put("grant_type"."authorization_code");
        System.out.println(params.toString());
        String respones = HttpUtil.post(url, params);
        JSONObject json = JSONUtil.parseObj(respones);
        System.out.println("json:"+json);
        String openId = json.getStr("openid");
        System.out.println("openid:"+openId);
        if(openId == null || openId.length() == 0) {throw new GoflyException("Temporary login credentials error");
        }
        returnopenId; }}Copy the code

The Controller layer

/** * User registration *@param form
 * @return* /
@apiOperation (value = "user register ",notes =" user register ")
@PostMapping("/register")
public GoflyRespones register(@Valid @RequestBody RegisterForm form){
    Integer num = registerService.save(form);
    if(num == 0) {return GoflyRespones.error("Failed to save user information");
    }
    return GoflyRespones.success("User information saved successfully");
}
Copy the code

5. Specific login process

We refer to the flow chart provided by the official small program for development

Description:

  1. Call wx.login() to get the temporary login credential code and pass it back to the developer server.
  2. Code2Session interface is called to obtain the unique identifier of the user OpenID, the unique identifier of the user under the account of wechat Open Platform UnionID (if the current small program has been bound to the account of wechat open platform) and session key session_key.

After that, the developer server can generate a custom login state according to the user id, which can be used to identify the user identity during the interaction between the front and back ends in the subsequent business logic. In my project, we save openId in the database

Note:

  1. The session keysession_keyUser dataEncrypted signatureThe key. For the security of the application’s own data, the developer serverThe session key should not be issued to applets, nor should it be provided externally.
  2. Temporary login credential code can only be used once

5.1 Small Program Obtaining temporary Authorization String

Because we are using uni framework in the project, the operations are as follows:

<template> <view> <! <image SRC ="... "<image SRC ="..." /.. PNG "class="logo" mode="widthFix"></image> <view class=" logoid "></ view> <button Class ="login-btn" open-type="getUserInfo" @tap="login()"> </button> </view> </template> <script> export default {class="login-btn" open-type="getUserInfo" @tap="login()"> data() { return { } }, methods: Uni. login({provider:"weixin", success:function(resp){ console.log("code:"+resp.code); Temporary license string under the / / TODO is to submit a request to the backend, for example: / / POST http://localhost:9000/login param: code = * * * * * * * * * * * * * * *}})}}} < / script >Copy the code

Back-end login logic:

/** * User request parameter */
@ApiModel
@Data
public class LoginParam {

    @APIModelProperty (value = "temporary authorization code ")
    @notblank (message = "temporary authorization code cannot be empty ")
    private String code;
}
Copy the code
    /** * User login *@paramParam backend validation *@return* /
    @apiOperation (value = "user login ",notes =" user login ")
    @PostMapping("/login")
    public GoflyRespones login(@Valid @RequestBody LoginParam param){
      	// Check whether the user exists
        Integer userId = userService.login(param.getCode());
        return GoflyRespones.success("Success").putData(jwtUtil.createToken(userId));
    }
Copy the code
@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Value("${gofly.wx.appid}")
    private String appId;

    @Value("${gofly.wx.app-secret}")
    private String appSecret;

    @Value("${gofly.wx.url}")
    private String url;

    /** * User login *@paramCode Temporary authorization string * obtained by the wechat applet@return* /
    @Override
    public Integer login(String code) {
        / / get the openId
        String openId = getOpenId(code);
        // Run openId to check whether the user exists
        Integer userId = userMapper.getUserIdByOpenId(openId);
        // If it does not exist, throw an exception, if it does, return userId
        if(userId==null) {throw new GoflyException("User does not exist");
        }
        return userId;
    }

    /** * Get user information based on user ID *@param userId
     * @return* /
    @Override
    public User selectUserByUserId(Integer userId) {
        return userMapper.selectUserByUserId(userId);
    }


    /** * Exchange openId * from wechat server with code@param code
     * @return* /
    private String getOpenId(String code){
        HashMap params = new HashMap();
        params.put("appid",appId);
        params.put("secret",appSecret);
        params.put("js_code",code);
        params.put("grant_type"."authorization_code");
        System.out.println(params.toString());
        String respones = HttpUtil.post(url, params);
        JSONObject json = JSONUtil.parseObj(respones);
        System.out.println("json:"+json);
        String openId = json.getStr("openid");
        System.out.println("openid:"+openId);
        if(openId == null || openId.length() == 0) {throw new GoflyException("Temporary login credentials error");
        }
        returnopenId; }}Copy the code
@Mapper
public interface UserMapper {
    public Integer getUserIdByOpenId(String openId);

    public User selectUserByUserId(Integer userId);
}
Copy the code
<! Select user id from openid;
  <select id="getUserIdByOpenId" resultType="int">
    select
      id
    from t_user
    where open_id = #{openId}
    and status = 1
  </select>

  <! -- Get user information from user ID -->
  <select id="selectUserByUserId" resultType="com.yangzinan.goflywx.entity.User">
    select
      <include refid="Base_Column_List"></include>
    from t_user
    where id = #{userId}
  </select>
Copy the code

5.2 Exchanging Temporary Authorization String for OpenId (Key)

    /** * Exchange openId * from wechat server with code@param code
     * @return* /
    private String getOpenId(String code){
        HashMap params = new HashMap();
        params.put("appid",appId);
        params.put("secret",appSecret);
        params.put("js_code",code);
        params.put("grant_type"."authorization_code");
        System.out.println(params.toString());
        String respones = HttpUtil.post(url, params);
        JSONObject json = JSONUtil.parseObj(respones);
        System.out.println("json:"+json);
        String openId = json.getStr("openid");
        System.out.println("openid:"+openId);
        if(openId == null || openId.length() == 0) {throw new GoflyException("Temporary login credentials error");
        }
        return openId;
    }
Copy the code

6. Project test

6.1 Applet Registration Test (updated April 21)

Click the login button to register the user successfully

POST http://localhost:9000/gofly-wx/pub/register 
Copy the code
{
  code: 200
  message: "User information saved successfully"
}
Copy the code

If the user already exists

{
   code: 500
   message: "Failed to save user information"
}
Copy the code

Run the small program to get the code

Use kinfe4J tests

Step 1: We first enter the wrong code

Step 2: We enter the correct code, login successfully and obtain the JwtToken

Step 3: Access the interface that requires authentication

Step 4: We carry the token request in the request parameters

The article is complete on April 20, 2021. I will upload the source code to Github later

Good night!