“This article has participated in the call for good writing activities, click to view: the back end, the big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!”

Good morning, everyone!

We’ve already talked about Shiro and JWT for authentication and user authorization, but today we’ll look at the combination of Security and JWT.

Introduction to the

Let’s talk about authentication and user authorization:

  • User Authentication (Authentication) : The system verifies the user name and password provided by the user to verify whether the user is a legitimate subject in the system, that is, whether the user can access the system.
  • User Authorization (Authorization) : The system assigns different roles to users to obtain corresponding permissions. That is, the system checks whether the user has the permission to perform this operation.

The Security of Web applications includes user authentication and user authorization. Spring Security (hereinafter referred to as Security), based on the Spring framework, can completely solve this problem.

Its real power is that it can be easily extended to meet custom requirements.

The principle of

SecurityYou can view it as a groupfilterFilter chain consists of permission authentication. Its entire workflow is shown below:



In the figure, the green authentication mode can be configured, and the orange and blue positions cannot be changed:

  • FilterSecurityInterceptor: The final filter that determines whether the current request is reachableController
  • ExceptionTranslationFilter: Exception filter. When receiving an exception message, users will be guided to authenticate.

In actual combat

Project preparation

We use the Spring Boot framework for integration.

1. Dependencies introduced by POM files

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion>  </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> < artifactId > mybatis - plus - the boot - starter < / artifactId > < version > 3.4.0 < / version > < / dependency > < the dependency > <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <! Alibaba </groupId> <artifactId>fastjson</artifactId> <version>1.2.74</version> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId>  </dependency>Copy the code

2. Application. Yml configuration

spring:
  application:
    name: securityjwt
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: JDBC: mysql: / / 127.0.0.1:3306 / cheetah? characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
    username: root
    password: 123456

server:
  port: 8080

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.itcheetah.securityjwt.entity
  configuration:
    map-underscore-to-camel-case: true

rsa:
  key:
    pubKeyFile: C:\Users\Desktop\jwt\id_key_rsa.pub
    priKeyFile: C:\Users\Desktop\jwt\id_key_rsa
Copy the code

3. SQL file

/**
* sys_user_info
**/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user_info
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_info`;
CREATE TABLE `sys_user_info`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL.PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;


/**
* product_info
**/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for product_info
-- ----------------------------
DROP TABLE IF EXISTS `product_info`;
CREATE TABLE `product_info`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `price` decimal(10.4) NULL DEFAULT NULL,
  `create_date` datetime(0) NULL DEFAULT NULL,
  `update_date` datetime(0) NULL DEFAULT NULL.PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;
Copy the code

Introduction of depend on

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <! Jsonwebtoken </groupId> <artifactId> JJWT </artifactId> <version>0.9.1</version> </dependency>Copy the code

After the introduction, the project will start as shown in the following figure:



User nameuserThe password is a character string shown in the preceding figure.

SecurityConfig class

// Enable global method security
@EnableGlobalMethodSecurity(prePostEnabled=true, securedEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    // Authentication failure handling class
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    // Provides the public and private key configuration classes
    @Autowired
    private RsaKeyProperties prop;

    @Autowired
    private UserInfoService userInfoService;
    
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // CSRF is disabled because sessions are not used
                .csrf().disable()
                // Authentication failure handling class
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // Token-based, so no session is required
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // Filter requests
                .authorizeRequests()
                .antMatchers(
                        HttpMethod.GET,
                        "/*.html"."/**/*.html"."/**/*.css"."/**/*.js"
                ).permitAll()
                // All requests other than the above require authentication
                .anyRequest().authenticated()
                .and()
                .headers().frameOptions().disable();
        // Add JWT filter
        httpSecurity.addFilter(new TokenLoginFilter(super.authenticationManager(), prop))
                .addFilter(new TokenVerifyFilter(super.authenticationManager(), prop));
    }

    // Specify the source of the authentication object
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        
        auth.userDetailsService(userInfoService)
        // The password passed from the front end will be encrypted, so from the database
        // The queried password must be encrypted, which is always the case
        // It is encrypted when the user registers.
        .passwordEncoder(passwordEncoder());
    }

    // Password encryption
    @Bean
    public BCryptPasswordEncoder passwordEncoder(a){
        return newBCryptPasswordEncoder(); }}Copy the code

Blocking rule

  • anyRequest: Matches all request paths
  • access:SpringElThe expression results intrueCan be accessed
  • anonymous: Anonymously accessible
  • ‘denyAll: users cannot access
  • fullyAuthenticated: Users are fully authenticated to have access (nonremember-meAutomatic login)
  • hasAnyAuthority: If there is a parameter, the parameter indicates the permission, then any of the permissions can be accessed
  • hasAnyRole: If there is a parameter, the parameter indicates the role, and any role can access it
  • hasAuthority: If there is a parameter, the parameter indicates the permission. The permission can be accessed
  • hasIpAddress: Indicates a parameter, if anyIPAddress if the userIPIf the value matches the parameter, it can be accessed
  • hasRole: If there is a parameter, which indicates the role, the role can access it
  • permitAll: Users can access it at any time
  • rememberMe: Permission to passremember-meLogged-in user access
  • authenticated: Accessible after login

Authentication failure handling class

/** * returns unauthorized */
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint.Serializable {

    private static final long serialVersionUID = -8970718410437077606L;

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e)
            throws IOException {
        int code = HttpStatus.UNAUTHORIZED;
        String msg = "Authentication failed, unable to access system resources, please log in first"; ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); }}Copy the code

The certification process

User-defined authentication filters


public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    private RsaKeyProperties prop;

    public TokenLoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        this.authenticationManager = authenticationManager;
        this.prop = prop;
    }

    / * * *@author cheetah
     * @descriptionLogin verification@date 2021/6/28 16:17
     * @Param [request, response]
     * @return org.springframework.security.core.Authentication
     **/
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            UserPojo sysUser = new ObjectMapper().readValue(request.getInputStream(), UserPojo.class);
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
            return authenticationManager.authenticate(authRequest);
        }catch (Exception e){
            try {
                response.setContentType("application/json; charset=utf-8");
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                PrintWriter out = response.getWriter();
                Map resultMap = new HashMap();
                resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
                resultMap.put("msg"."Wrong username or password!");
                out.write(new ObjectMapper().writeValueAsString(resultMap));
                out.flush();
                out.close();
            }catch (Exception outEx){
                outEx.printStackTrace();
            }
            throw newRuntimeException(e); }}/ * * *@author cheetah
     * @descriptionLogin successful callback *@date 2021/6/28 16:17
     * @Param [request, response, chain, authResult]
     * @return void
     **/
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        UserPojo user = new UserPojo();
        user.setUsername(authResult.getName());
        user.setRoles((List<RolePojo>)authResult.getAuthorities());
        // Encrypt with private key: The token is valid for one day
        String token = JwtUtils.generateTokenExpireInMinutes(user, prop.getPrivateKey(), 24 * 60);
        response.addHeader("Authorization"."Bearer "+token);
        try {
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out = response.getWriter();
            Map resultMap = new HashMap();
            resultMap.put("code", HttpServletResponse.SC_OK);
            resultMap.put("msg"."Approved!");
            resultMap.put("token", token);
            out.write(new ObjectMapper().writeValueAsString(resultMap));
            out.flush();
            out.close();
        }catch(Exception outEx){ outEx.printStackTrace(); }}}Copy the code

process

SecurityThe default login path is/loginWhen we call this interface, it will call the aboveattemptAuthenticationMethods;









So we’re going to customize itUserInfoServiceinheritanceUserDetailsServiceimplementationloadUserByUsernameMethods;

public interface UserInfoService extends UserDetailsService {}@Service
@Transactional
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    private SysUserInfoMapper userInfoMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserPojo user = userInfoMapper.queryByUserName(username);
        returnuser; }}Copy the code

LoadUserByUsername returns the UserDetails type, so UserPojo inherits the UserDetails class

@Data
public class UserPojo implements UserDetails {

    private Integer id;

    private String username;

    private String password;

    private Integer status;

    private List<RolePojo> roles;

    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // Ideal type returns admin permission and can handle this by itself
        List<SimpleGrantedAuthority> auth = new ArrayList<>();
        auth.add(new SimpleGrantedAuthority("ADMIN"));
        return auth;
    }

    @Override
    public String getPassword(a) {
        return this.password;
    }

    @Override
    public String getUsername(a) {
        return this.username;
    }

    /** * Whether the account has expired **/
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired(a) {
        return true;
    }

    /** * Whether to disable */
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked(a) {
        return true;
    }

    /** * Whether the password expires */
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired(a) {
        return true;
    }

    /** * Whether to enable */
    @JsonIgnore
    @Override
    public boolean isEnabled(a) {
        return true; }}Copy the code

When the certification is approved, it will be inSecurityContextSet in theAuthenticationObject to call back tosuccessfulAuthenticationMethod returnstokenInformation,

The overall flow chart is as follows

Authentication process

User-defined token filter

public class TokenVerifyFilter extends BasicAuthenticationFilter {
    private RsaKeyProperties prop;

    public TokenVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop) {
        super(authenticationManager);
        this.prop = prop;
    }

    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        String header = request.getHeader("Authorization");
        if (header == null| |! header.startsWith("Bearer ")) {
            // If the token is incorrect, the user will be prompted to login!
            chain.doFilter(request, response);
        } else {
            // If you carry a token in the correct format, get the token first
            String token = header.replace("Bearer "."");
            // Decryption via public key: verify that tken is correct
            Payload<UserPojo> payload = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserPojo.class);
            UserPojo user = payload.getUserInfo();
            if(user! =null){
                UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
                // Save the authentication information to the security contextSecurityContextHolder.getContext().setAuthentication(authResult); chain.doFilter(request, response); }}}}Copy the code

Need to be in when we visitheaderCarried intokeninformation

As for the JWT generation token and RSA generation of public key, private key part, can be viewed in the source code, reply to “SJWT” can obtain the complete source code yo!

The above is all the content of today, if you have different opinions or better idea, welcome to contact AH Q, add AH Q can join the technical exchange group to participate in the discussion!

Background message to get Java dry goods materials: study notes and big factory interview questions