Travelers have recently spent a lot of time researching the use of Spring Security, in addition to completing daily mandates.

Today, Can li hurried to find the traveler, said he studied many kinds of bombs, but with the type of bombs, the number of increases, can li feel more and more difficult to manage their “baby”, want to ask travelers to help.

Seeing her “please” look, the traveler decides to say yes.

The traveler decided to build a “jump bomb” management system with Spring Security, which he had studied in recent days, and Vue.

At the same time, Can worry about being found by the head of the piano specific “baby” type and number, they let travelers do the management system can only be operated by herself, others can not view or modify.

Li good partner 77, Diona and early pomelo also heard that Li recently developed a lot of types of bombs, are very curious, Li also decided to let travelers will they also join the management system, but Li only let them query bomb information, can not be modified.

Without further ado, the task was clear. The traveler took out his laptop, sat in the cattail tavern and began to sort out his thoughts. He opened vscode and idea and began to work.

1. Idea? To sort out! Comb!

Before making the management system, travelers decided to comb through their thoughts.

Before that, we still need to learn some basic principles of user login so that we can read this article better. If you are not familiar with it, you can check out this article: Links

Now that travelers are using Spring Security as a Security framework, let’s take a look at how it works.

At the heart of Spring Security’s login authentication process is the filter chain. When a request arrives, it is processed in the order of the filter chain and verified by all the filter chains before accessing our API.

At the same time, Spring Security provides a variety of filters to enable many different authentication modes:

  • UsernamePasswordAuthenticationFilterLogin authentication mode using the user name and password
  • SmsCodeAuthenticationFilterThe SMS verification code is used for login authentication
  • SocialAuthenticationFilterLogin authentication using social media mode
  • Oauth2AuthenticationProcessingFilterUse Oauth2 authentication

Today, the traveler decided to use UsernamePasswordAuthenticationFilter way to achieve this management system login authentication.

Take a quick look at the certification process:

It doesn’t matter if you don’t understand it, we don’t need to understand all the logic of the classes, the important parts will be covered below.

However, it is a separate system, that is, Spring Security is no longer responsible for page jumps, but for interface access management, and the front-end VUE is responsible for page jumps.

By default, successful login, failed login, exit login, 403, and so on in Spring Security are redirected to a dedicated page (request forwarding by default).

However, since it is a separate system, Spring Security can only send a JSON response to the front end. After receiving the response, the front end controls the page redirect according to the request content.

In addition, after a successful login, the back end returns the user name for the front-end storage, the back end also needs to write an interface to determine whether the user is logged in and join interception. In this way, each time the front end visits the interface to judge whether to log in or not, the back end will return the user data to the front end for display if logged in, but the back end will be intercepted and return to 403 if not logged in, and the front end will jump to the login page.

To be clear, we only let Spring Security control access to individual interfaces, user logins, permissions, and so on. For each operation, the back end returns a JSON data to the front end, which determines whether the operation is successful and controls the jump and display.

2. Dependence? Be ready! Set up!

First, create a Spring Boot project, check Security, and MyBatis dependencies, and configure the data source.

You can also add Spring Security dependencies to pom.xml later:

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

In addition, we need to use the JSON tool of Fastjson:

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>fastjson</artifactId>
   <version>1.2.73</version>
</dependency>
Copy the code

Then launch the project, access your address, and you’ll find a login page because Spring Security blocks all requests by default.

So, there are a lot of things we need to fix.

3. Data? The model! Build!

Now we need to build the data model and database tables.

RBAC (role-based permission management) is definitely used in this system.

In other words, by establishing the mapping between users and roles, each user can have multiple roles and each role can have multiple permissions. Users can perform operations and access resources based on their roles.

A basic RBAC class diagram looks like this:

A user can have multiple roles, and one role can be owned by multiple users. A role can have multiple rights, and a right can be owned by multiple roles.

That is, there is a many-to-many relationship between users and roles, and a many-to-many relationship between roles and permissions.

Why add a role between user and permission? Isn’t that superfluous? In fact, this is necessary to facilitate future expansion of the system, as well as flexible permission control.

Based on this, we can design the database, for the many-to-many database model building and MyBatis cascade if you are not familiar, you can first look at this article: links

In this management system, there are two roles: administrator (admin) and visitor (visitor), where the administrator can add (addBomb) and query (queryBomb) bombs, and the visitor can only query (queryBomb) bombs.

Here the administrator, the visitor is the role, increase, query bomb is permission.

Create a package named DataObject and create the user classes MyUser, Role, and Permission classes in it as follows:

package com.example.securitytest.dataobject;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * 用户
 */
@Setter
@Getter
@NoArgsConstructor
public class MyUser implements Serializable {

   /** * primary key id */
   private int id;

   /** * User name */
   private String username;

   /** * Password */
   private String password;

   /** ** nickname */
   private String nickname;

   /** ** avatar */
   private String avatar;

   /** * User role */
   private Set<Role> roles;

   /** * Obtain all permissions for the user **@returnUser permissions */
   public Set<Permission> getPermissions(a) {
      Set<Permission> permissions = new HashSet<>();
      for (Role role : roles) {
         for(Permission permission : role.getPermissions()) { permissions.add(permission); }}returnpermissions; }}Copy the code
package com.example.securitytest.dataobject;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.util.List;
import java.util.Set;

/** * Role class */
@Getter
@Setter
@NoArgsConstructor
public class Role implements Serializable {

   /** * primary key id */
   private int id;

   /** * Role name */
   private String name;

   /** * The user who has the role */
   private List<MyUser> myUsers;

   /** * Permissions of this role */
   private Set<Permission> permissions;

}
Copy the code
package com.example.securitytest.dataobject;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.util.Set;

/** * permission */
@Getter
@Setter
@NoArgsConstructor
public class Permission implements Serializable {

   /** * primary key id */
   private int id;

   /** * Permission name */
   private String name;

   /** * Role with this permission */
   private Set<Role> roles;

}
Copy the code

Then create the corresponding MyBatis Mapper class and XML, here only to achieve the increase and query two functions, in this does not paste the code, and finally will give the address of the sample warehouse.

In the meantime, we’d be better off building a return results model specifically for returning results to the front end. Create class Result ();

package com.example.securitytest.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.io.Serializable;

@Setter
@Getter
@NoArgsConstructor
public class Result<T> implements Serializable {

   /** * message */
   private String message;

   /** * Whether the operation is successful */
   private boolean success;

   /** * The data body returned (the content returned) */
   private T data;

   /** * sets the result to success **@paramMSG message *@paramData Data body */
   public void setResultSuccess(String msg, T data) {
      this.message = msg;
      this.success = true;
      this.data = data;
   }

   /** * sets the result to fail **@paramMSG news * /
   public void setResultFailed(String msg) {
      this.message = msg;
      this.success = false;
      this.data = null; }}Copy the code

The content that Kelly is managing is bombs, so you also need to create the bomb entity class and its corresponding database tables.

Ok, so the data model is built, the DAO layer is built, and finally the Control layer is defined to write the API to get the data, so I’ll omit the API code here.

Next, we are going to set up the user login service.

Here is the SQL file:

Initialize the user table
drop table if exists `my_user`;
create table `my_user`
(
   `id`       int          not null,
   `username` varchar(16)  not null unique,
   `password` varchar(64)  not null,
   `nickname` varchar(16)  not null,
   `avatar`   varchar(128) not null.primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

drop table if exists `role`;
create table `role`
(
   `id`   int         not null,
   `name` varchar(16) not null unique.primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

drop table if exists `user_role`;
create table `user_role`
(
   `user_id` int not null,
   `role_id` int not null
) engine = InnoDB
  default charset = utf8mb4;

drop table if exists `permission`;
create table `permission`
(
   `id`   int         not null,
   `name` varchar(16) not null unique.primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

drop table if exists `role_permission`;
create table `role_permission`
(
   `role_id`       int not null,
   `permission_id` int not null
) engine = InnoDB
  default charset = utf8mb4;

drop table if exists `bomb`;
create table `bomb`
(
   `id`   int unsigned auto_increment,
   `name` varchar(32) not null,
   `type` varchar(32) not null.primary key (`id`)
) engine = InnoDB
  default charset = utf8mb4;

Initialize test data
insert into `my_user`
values (0.'klee'.'$2a$10$uifIuXG6FzrI.NqhhZN0H.PziUzqn78Nwq7Dg9C2V4Fa3nXNdZj5e'.'but she'.'/avatar/klee.jpg'),    -- Password: 123456
      (1.'qiqi'.'$2a$10$4QRxlo/YuR8ZdFJSW4uV3uT9HqUxZgad5oeQHuHy2nfS4en/Z8dQ2'.'groups'.'/avatar/qiqi.jpg'),    -- Password: 789101112
      (2.'diona'.'$2a$10$Q8QQb4s9Iy12qCDmiw0Qwe8/TvCOolKgaylPAus5kE5E5k/cp.2km'.'Diona'.'/avatar/diona.jpg'), -- Password: 13141516
      (3.'sayu'.'$2a$10$uifIuXG6FzrI.NqhhZN0H.PziUzqn78Nwq7Dg9C2V4Fa3nXNdZj5e'.'early pomelo'.'/avatar/sayu.jpg');
-- Password: 123456

Spring Security roles must start with "ROLE_"
insert into `role`
values (0.'ROLE_admin'),
      (1.'ROLE_visitor');

-- Koli is the administrator, Qiqi, Diona and Early grapefruit are all visitors
insert into `user_role`
values (0.0),
      (1.1),
      (2.1),
      (3.1);

insert into `permission`
values (0.'queryBomb'),
      (1.'addBomb');

Administrators can view, add bombs, visitors can only view
insert into `role_permission`
values (0.0),
      (0.1),
      (1.0);

For the sake of simplicity, we will only do the name and type of bombs today, not quantity management for the time being
insert into `bomb` (name, type)
values ('Little Bomb'.'booby trap'),
      (Count Tutu..'satire'),
      ('Bouncing bomb'.'Cluster bombs');
Copy the code

4. Service? Logic! Rewrite!

Spring Security has its own logics for user authentication, login success/failure, login exit, and so on, which we need to rewrite and customize to meet actual business needs.

(1) Custom user name and password login interceptor

Separation system as it was before and after the end, we usually send json data to the back-end to log in, but only support form format by default, this time we need to rewrite UsernamePasswordAuthenticationFilter, Re-implement the attemptAuthentication method, which is used to retrieve the user name and password from the front-end request and submit it to subsequent authentication.

Create a package filter, create MyAuthFilter class inside, implement custom authentication filter, first give my code:

package com.example.securitytest.filter; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.MediaType; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Map; /** * Custom authentication filter, To implement the before and after the separation of the json format login request parsing * / public class MyAuthFilter extends UsernamePasswordAuthenticationFilter {/ * * * username field name * / in the request body private String usernameParameter = "username"; /** * private String passwordParameter = "password"; Public MyAuthFilter() {} public MyAuthFilter() {} Username and password field name * * @param usernameParameter User-defined username field name * @param passwordParameter User-defined password field name */ public MyAuthFilter(String) usernameParameter, String passwordParameter) { this.usernameParameter = usernameParameter; this.passwordParameter = passwordParameter; } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse Response) throws AuthenticationException {// Checks whether the data is of the JSON type if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {requestBody <String, String> requestBody = null; RequestBody = new ObjectMapper().readValue(request.getinputStream (), map.class); } catch (Exception e) { e.printStackTrace(); } // Get the username/password field value in the request body, And perform authentication UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(requestBody.get(usernameParameter), requestBody.get(passwordParameter)); / / return the certification result return enclosing getAuthenticationManager () authenticate (auth); } else {/ / otherwise, use the default login form way return. Super attemptAuthentication (request, response); }}}Copy the code

I also added usernameParameter and passwordParameter above to implement custom username/password field names in front-end login authentication requests.

Of course, now that we have implemented a custom interceptor, do we need to configure it into Spring Security? Yes! Don’t worry now! These self-implemented classes will be configured together later.

(2) User-defined user data query logic

Create a class that represents the user data query logic. This class needs to implement the loadUserByUsername method in the UserDetailsService interface.

package com.example.securitytest.service.impl;

import com.example.securitytest.dao.MyUserDAO;
import com.example.securitytest.dataobject.MyUser;
import com.example.securitytest.dataobject.Permission;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.Set;

/** * Implement Spring Security login logic */
@Service
public class MyUserServiceImpl implements UserDetailsService {

   @Autowired
   private MyUserDAO myUserDAO;

   /** * fetch user **@paramUsername username */
   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      MyUser myUser = null;
      try {
         myUser = myUserDAO.getByUsername(username);
      } catch (Exception e) {
         e.printStackTrace();
      }
      if (myUser == null) {
         throw new UsernameNotFoundException("Can't find users!");
      }
      // Assemble permission
      Set<GrantedAuthority> authorities = new HashSet<>();
      for (Permission permission : myUser.getPermissions()) {
         authorities.add(new SimpleGrantedAuthority(permission.getName()));
      }
      return newUser(myUser.getUsername(), myUser.getPassword(), authorities); }}Copy the code

First of all, there is a User class in Spring Security that represents User objects, but in practice we have to build our own User model (MyUser in my case) because the built-in User class does not meet business needs.

The User has a three-parameter constructor: username, password, and permission.

But what exactly is loadUserByUsername? As the name implies, this method must be used to query user information from the database based on the user name (credentials).

Therefore, we simply query our own User information from the database and extract the username, password, and permission or role fields from it into an instance of the User class and return them. There is no need to implement password matching here.

Note that in Spring Security, roles and permissions are treated as GrantedAuthority, but the role name must begin with ROLE_! This is how Spring Security determines both roles and permissions, as noted in the previous SQL file.

In this case, the method returns the corresponding user information and compares it with the information from the front end.

(3) Customize the logic after successful login

By default, a successful login redirects to a page, but now we need to send json data after a successful login.

Build packet handler and create AuthenticationSuccessHandler classes, this class to implement AuthenticationSuccessHandler onAuthenticationSuccess method of interface. Here’s the code:

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/** * Custom logon success processor logic */
public class MyAuthSuccessHandler implements AuthenticationSuccessHandler {

   @Override
   public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
      // Set the response status code to 200
      httpServletResponse.setStatus(HttpServletResponse.SC_OK);
      // Set the response content to utF-8 encoded JSON
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // Get the user name to return to front-end local storage
      UserDetails getUser = (UserDetails) authentication.getPrincipal();
      // Assemble your own result object
      Result<String> result = new Result();
      result.setResultSuccess("Login successful!", getUser.getUsername());
      // Serialize the result object to JSON
      String resultJSON = JSON.toJSONString(result);
      // Write the response bodyPrintWriter writer = httpServletResponse.getWriter(); writer.write(resultJSON); writer.flush(); writer.close(); }}Copy the code

In this method, there is an HttpServletRequest type parameter representing the received request object, an HttpServletResponse type parameter representing the returned object, and an Authentication type parameter representing the authenticated user information, including the user name, permission, user login IP address, and so on.

(4) Customize logics after login failures

A login failure also requires a JSON return.

We are still in the handler to establish MyAuthFailureHanlder, need to implement interface AuthenticationFailureHandler onAuthenticationFailure method.

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/** * Custom authentication failed processor */
public class MyAuthFailureHanlder implements AuthenticationFailureHandler {

   @Override
   public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
      // Set the response status code to 200
      httpServletResponse.setStatus(HttpServletResponse.SC_OK);
      // Set the response content to utF-8 encoded JSON
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // Assemble your own result object
      Result result = new Result();
      result.setResultFailed("Wrong username or password!");
      // Serialize the result object to JSON
      String resultJSON = JSON.toJSONString(result);
      // Write the response bodyPrintWriter writer = httpServletResponse.getWriter(); writer.write(resultJSON); writer.flush(); writer.close(); }}Copy the code

Similar to the successful login method, I won’t go into too much detail here.

(5) Customize 403 no permission access logic

To create a MyAccessDeniedHandler in handler, implement the Handle method of AccessDeniedHandler.

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/** * The custom permission is insufficient processor */
public class MyAccessDeniedHandler implements AccessDeniedHandler {

   @Override
   public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
      // Set the response status code to 403
      httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
      // Set the response content to utF-8 encoded JSON
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // Assemble your own result object
      Result result = new Result();
      result.setResultFailed("Insufficient permissions! Please contact the administrator!");
      // Serialize the result object to JSON
      String resultJSON = JSON.toJSONString(result);
      // Write the response bodyPrintWriter writer = httpServletResponse.getWriter(); writer.write(resultJSON); writer.flush(); writer.close(); }}Copy the code

By default Spring Security intercepts all requests, which triggers the unauthorized method. Later we need to configure the specific path to intercept.

(6) Customize the login success logic

MyLogoutSuccessHanlder is established in handler to implement onLogoutSuccess method of interface LogoutSuccessHandler.

package com.example.securitytest.handler;

import com.alibaba.fastjson.JSON;
import com.example.securitytest.model.Result;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/** * Custom logout success handler */
public class MyLogoutSuccessHanlder implements LogoutSuccessHandler {

   @Override
   public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
      // Set the response status code to 200
      httpServletResponse.setStatus(HttpServletResponse.SC_OK);
      // Set the response content to utF-8 encoded JSON
      httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
      httpServletResponse.setCharacterEncoding("utf-8");
      // Result of assembly
      Result result = new Result();
      result.setResultSuccess("Exit successful!".null);
      // Serialize the result object to JSON
      String resultJSON = JSON.toJSONString(result);
      // Write the response bodyPrintWriter writer = httpServletResponse.getWriter(); writer.write(resultJSON); writer.flush(); writer.close(); }}Copy the code

As you can see, the xxxHandler classes (logon success, logon failure, no access, logon exit logic) are all very similar and return JSON in the same way, mainly by assembling our own result object, serializing it as a JSON string and writing it to the returned response stream.

5. Security? Strategy! Configuration!

The above defines a set of custom implementation classes that now need to be configured. In addition, we also need to configure the interception path, each interface path access permissions.

New config package, inside the new SecurityConfig class, inheritance WebSecurityConfigurerAdapter and rewrite the configure method. Code first:

package com.example.securitytest.config;

import com.example.securitytest.filter.MyAuthFilter;
import com.example.securitytest.handler.MyAccessDeniedHandler;
import com.example.securitytest.handler.MyAuthFailureHanlder;
import com.example.securitytest.handler.MyAuthSuccessHandler;
import com.example.securitytest.handler.MyLogoutSuccessHanlder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/** * Spring Security configuration */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

   /** * Configures the password encryptor */
   @Bean
   public PasswordEncoder passwordEncoder(a) {
      return new BCryptPasswordEncoder();
   }

   /** * Configure the custom username password interceptor to Bean */
   @Bean
   public UsernamePasswordAuthenticationFilter myAuthFilter(a) throws Exception {
      // Create your own instance of username and password interceptor
      UsernamePasswordAuthenticationFilter myAuthFilter = new MyAuthFilter();
      // Note that since it is a custom login interceptor, the login interface address must be configured here!
      myAuthFilter.setFilterProcessesUrl("/api/login");
      // Set as a custom login success/failure handler
      myAuthFilter.setAuthenticationSuccessHandler(new MyAuthSuccessHandler());
      myAuthFilter.setAuthenticationFailureHandler(new MyAuthFailureHanlder());
      myAuthFilter.setAuthenticationManager(authenticationManagerBean());
      return myAuthFilter;
   }

   /** * Configure a security interception policy */
   @Override
   protected void configure(HttpSecurity httpSecurity) throws Exception {
      // Set the authentication interceptor
      httpSecurity.authorizeRequests()
            // Set the bomb list permission
            .antMatchers("/api/get").hasAuthority("queryBomb")
            // Set permissions to add bombs
            .antMatchers("/api/add").hasAuthority("addBomb")
            // You need to log in to obtain user information
            .antMatchers("/api/islogin").authenticated()
            // Pass avatar URL
            .antMatchers("/avatar/*").permitAll();
      // Customize the logout login URL and configure a customized logout success handler
      httpSecurity.logout().logoutUrl("/api/logout").logoutSuccessHandler(new MyLogoutSuccessHanlder());
      / / close CSRF
      httpSecurity.csrf().disable();
      // Set your own login authentication interceptor
      httpSecurity.addFilterAt(myAuthFilter(), UsernamePasswordAuthenticationFilter.class);
      // Set to custom insufficient processor
      httpSecurity.exceptionHandling().accessDeniedHandler(newMyAccessDeniedHandler()); }}Copy the code

First, define the above passwordEncoder method to be injected into the Bean, and configure the password encryptor. Generally, configure the BCryptPasswordEncoder as above.

Then, the above method, myAuthFilter, is the username and password interceptor method we use to configure ourselves, injecting it into the Bean. It is also configured with the login request path to which the login information request is sent, and our custom login success/failure interceptor.

The focus is on the following configure method, which uses the HttpSecurity parameter to do the configuration.

You can see that its methods are called chained, where:

  • authorizeRequestsConfigure the path to authenticate and its access permissions, then:
    • antMatchersMatch the path and set the access permission for the path:
      • hasAuthorityYou can access the path only when you have specified permission
      • hasAnyAuthoritySpecify multiple permissions to access the path as long as you have one of them
      • hasRoleYou can access the path only when you have a specified role
      • hasAnyRoleSpecify multiple roles that can access the path as long as they have one of them
      • hasIpAddressOnly the specified IP address can access the path
      • authenticatedYou can access the path as long as you are logged in
      • permitAllThe path is not intercepted
  • logoutConfigure log out and then:
    • logoutUrlConfigure the request path for logging out API (to which logging out requests are sent)
    • logoutSuccessHandlerSet the handler for logging out successfully, and fill in the instance of our custom logout successfully implementation class above
  • addFilterAtCustom interceptor, above defines our custom user name and password login interceptor, can be set here. The first parameter is an instance of our interceptor, where the method has been definedmyAuthFilterThe second parameter indicates the type of interceptor to set, which we useUsernamePasswordAuthenticationFilter
  • exceptionHandlingConfigure exception handling, and then:
    • accessDeniedHandlerSet custom 403 logic

Note that travelers have found many tutorials on the web about this configure method, login path, login success, login failure logic using the HttpSecurity parameter, but why not here? Since we have a custom user name and password to log in to the interceptor, we need to set it in the custom interceptor instance and then configure the custom interceptor instance to HttpSecurity as above.

The role name must start with ROLE_. If hasRole or hasAnyRole is used, the parameter must not start with ROLE_. Otherwise, an error will be reported. For example, if only ROLE_admin is allowed, hasRole(“admin”) should be written.

In this way, from the customization of some processing logic, to configuration, complete!

6. Web pages? The front! Open dry!

Traveler has finally finished building the back end and is now building the front end.

Since this article is mainly about using Spring Security, it will not explain how to write the front end, but just give some ideas.

Here is the front end separation mode of Vue. The front end sends the request through AXIos, gets the result, and controls the page jump, display, and so on.

Every page load time back the judge whether the user login interface sends a request to judge whether the login, also visible above configuration security policy stopped the judge whether the login interface (/ API/islogin), the interface essence is username query information, specific can see my project source code (below warehouse address), So if 403 is returned, you’re not logged in. If 200 is returned, you’re logged in.

Of course, every time you log in, the back end will return the user name, the front end uses localStorage to store the user name, the front end uses this user name to access the back end to determine the login interface to achieve login judgment.

Of course, this is my idea, and you can do it yourself.

Here we use the Vue multi-page application building. If you are not familiar with the Vue multi-page application, look at this link

Here are some of the effects!

Login screen

Kelly can add and view the bomb

Pomelo can only view bombs, but not add them

7. Done? Leave work! Conclusion!

At this point, the first version of the traveler to Kaili “bomb management system” is complete! Here for the sake of simplicity, there is no quantity management, user registration and so on.

However, in this case, some of our Spring Security logic needs to be set up in our own way.

The core idea is:

  • The backend controls only access to resources, such as interfaces, data, and user logins
  • For each operation, the back end returns JSON data to the front end rather than performing request forwarding or redirecting
  • Let the front end read the results to judge the success or failure of the operation, to achieve the jump display and so on

Sample repository address