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:
- Call wx.login() to get the temporary login credential code and pass it back to the developer server.
- 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:
- The session key
session_key
User 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. - 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!