Abstract

This paper mainly explains that Mall integrates SpringSecurity and JWT to realize the login and authorization functions of background users, and at the same time, revamp the configuration of Swagger-UI so that it can automatically remember the login token and send.

Project use framework introduction

SpringSecurity

SpringSecurity is a powerful and highly customizable authentication and authorization framework that is a set of Web security standards for Spring applications. SpringSecurity focuses on providing authentication and authorization capabilities for Java applications and, like all Spring projects, is highly extensible for custom requirements.

JWT

JWT is an abbreviation for JSON WEB TOKEN. JWT is a secure JSON object defined by RFC 7519. It is trusted and secure due to the use of digital signatures.

The composition of JWT

  • The JWT token is in the format of header.payload-signature
  • Header is used to store the signature generation algorithm
{"alg": "HS512"}
Copy the code
  • Payload Stores the user name, generation time, and expiration time of the token
{"sub":"admin"."created":1489079981393."exp":1489684781}
Copy the code
  • Signature indicates the signature generated using header and payload. If the header and payload are tampered with, authentication fails
//secret indicates the encryption algorithm key
String signature = HMACSHA512(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
Copy the code

JWT instance

This is a JWT string

eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImNyZWF0ZWQiOjE1NTY3NzkxMjUzMDksImV4cCI6MTU1NzM4MzkyNX0.d-iki0193X0bBOETf2UN3r3 PotNIEAV7mzIxxeI5IxFyzzkOZxS0PGfF_SK6wxCv2K8S0cZjMkv6b5bCqc0VBwCopy the code

The results can be obtained at jwt.io/

JWT implements authentication and authorization principles

  • The user invokes the login interface and obtains the JWT token after successful login.
  • After that, each time the user invokes the interface, he adds an Authorization header to the HTTP header, which is a TOKEN with a value of JWT.
  • The background program decodes the information in the Authorization header and verifies the digital signature to obtain the user information, so as to realize authentication and Authorization.

Hutool

Hutool is a rich Java open source toolkit that helps us simplify every line of code and eliminate every method. The Mall project uses this toolkit.

Project usage table description

  • ums_admin: Background user table
  • ums_role: Indicates the background user role list
  • ums_permission: Indicates the background user permission table
  • ums_admin_role_relation: indicates the relationship table between background users and roles. The relationship between users and roles is many-to-many
  • ums_role_permission_relation: indicates the relationship between background user roles and permissions. Roles and permissions are many-to-many
  • ums_admin_permission_relation: indicates the background user and permission relationship table (excluding the permissions defined in roles). Add permission indicates that a user has more permissions than a role, and subtract permission indicates that a user has fewer permissions than a role

Integrate SpringSecurity and JWT

Add project dependencies in POM.xml

<! --SpringSecurity dependency configuration -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<! --Hutool Java Toolkit -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>4.5.7</version>
</dependency>
<! --JWT(Json Web Token) login support -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
Copy the code

Add JWT token utility classes

Utility classes for generating and parsing JWT tokens

Description of related methods:

  • GenerateToken (UserDetails UserDetails) : Used to generate the token based on the login user information
  • GetUserNameFromToken (String token) : Obtains information about the login user from the token
  • ValidateToken (String Token, UserDetails UserDetails) : Checks whether the token is still valid
package com.macro.mall.tiny.common.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/** * Created by macro on 2018/4/26. */
@Component
public class JwtTokenUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private Long expiration;

    /** * based on the token responsible for generating the JWT */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /** * get the payload in JWT from token */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            LOGGER.info("JWT format validation failed :{}",token);
        }
        return claims;
    }

    /** * Expiration time of token generation */
    private Date generateExpirationDate(a) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /** * Obtain the login user name from the token */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username =  claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /** * Verify that the token is still valid **@paramToken Indicates the token * passed by the client@paramUserDetails User information queried from the database */
    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUserNameFromToken(token);
        returnusername.equals(userDetails.getUsername()) && ! isTokenExpired(token); }/** * Check whether the token is invalid */
    private boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

    /** * Get expiration time from token */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /** * Generate token */ based on user information
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        return generateToken(claims);
    }

    /** * Check whether the token can be refreshed */
    public boolean canRefresh(String token) {
        return! isTokenExpired(token); }/** * Refresh token */
    public String refreshToken(String token) {
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATED, new Date());
        returngenerateToken(claims); }}Copy the code

Add a configuration class for SpringSecurity

package com.macro.mall.tiny.config;

import com.macro.mall.tiny.component.JwtAuthenticationTokenFilter;
import com.macro.mall.tiny.component.RestAuthenticationEntryPoint;
import com.macro.mall.tiny.component.RestfulAccessDeniedHandler;
import com.macro.mall.tiny.dto.AdminUserDetails;
import com.macro.mall.tiny.mbg.model.UmsAdmin;
import com.macro.mall.tiny.mbg.model.UmsPermission;
import com.macro.mall.tiny.service.UmsAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.List;


/** * Created by macro on 2018/4/26. */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UmsAdminService adminService;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.csrf()// Since we are using JWT, we do not need CSRF here
                .disable()
                .sessionManagement()// Token-based, so no session is required
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, // Allow unauthorized access to static resources on the site
                        "/"."/*.html"."/favicon.ico"."/**/*.html"."/**/*.css"."/**/*.js"."/swagger-resources/**"."/v2/api-docs/**"
                )
                .permitAll()
                .antMatchers("/admin/login"."/admin/register")// Allow anonymous access to login registrations
                .permitAll()
                .antMatchers(HttpMethod.OPTIONS)// Cross-domain requests start with an options request
                .permitAll()
AntMatchers ("/**")// Test all run access
// .permitAll()
                .anyRequest()// All requests other than the above require authentication
                .authenticated();
        // Disable caching
        httpSecurity.headers().cacheControl();
        // Add JWT filter
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        // Add custom unauthorized and unlogged results are returned
        httpSecurity.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public PasswordEncoder passwordEncoder(a) {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public UserDetailsService userDetailsService(a) {
        // Obtain the login user information
        return username -> {
            UmsAdmin admin = adminService.getAdminByUsername(username);
            if(admin ! =null) {
                List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId());
                return new AdminUserDetails(admin,permissionList);
            }
            throw new UsernameNotFoundException("Wrong username or password");
        };
    }

    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(a){
        return new JwtAuthenticationTokenFilter();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean(a) throws Exception {
        return super.authenticationManagerBean(); }}Copy the code

Related dependencies and method description

  • Configure (HttpSecurity HttpSecurity) : used to configure the URL path to be intercepted, JWT filter, and processor after exceptions.
  • Configure (AuthenticationManagerBuilder auth) : used to configure UserDetailsService and PasswordEncoder;
  • RestfulAccessDeniedHandler: when the user does not have the access rights of microprocessor, used to return to the processing result of the JSON format;
  • RestAuthenticationEntryPoint: when did not login or token fails, return the result of the JSON format;
  • UserDetailsService: SpringSecurity define the core interface for according to the user name for user information, need to implement;
  • UserDetails: SpringSecurity defines classes that encapsulate user information (mainly user information and permissions) and needs to be implemented by itself;
  • PasswordEncoder: An interface defined by SpringSecurity to encode and compare passwords. Currently, BCryptPasswordEncoder is used.
  • JwtAuthenticationTokenFilter: check the user name and password before adding filter, if there is a token of JWT, themselves according to the token information to log in.

Add RestfulAccessDeniedHandler

package com.macro.mall.tiny.component;

import cn.hutool.json.JSONUtil;
import com.macro.mall.tiny.common.api.CommonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

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

** Created by macro on 2018/4/26. */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage()))); response.getWriter().flush(); }}Copy the code

Add RestAuthenticationEntryPoint

package com.macro.mall.tiny.component;

import cn.hutool.json.JSONUtil;
import com.macro.mall.tiny.common.api.CommonResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

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

/** * Created by macro on 2018/5/14. */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage()))); response.getWriter().flush(); }}Copy the code

Add AdminUserDetails

package com.macro.mall.tiny.dto;

import com.macro.mall.tiny.mbg.model.UmsAdmin;
import com.macro.mall.tiny.mbg.model.UmsPermission;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/** * Created by macro on 2018/4/26. */
public class AdminUserDetails implements UserDetails {
    private UmsAdmin umsAdmin;
    private List<UmsPermission> permissionList;
    public AdminUserDetails(UmsAdmin umsAdmin, List<UmsPermission> permissionList) {
        this.umsAdmin = umsAdmin;
        this.permissionList = permissionList;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // Returns the permissions of the current user
        returnpermissionList.stream() .filter(permission -> permission.getValue()! =null)
                .map(permission ->new SimpleGrantedAuthority(permission.getValue()))
                .collect(Collectors.toList());
    }

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

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

    @Override
    public boolean isAccountNonExpired(a) {
        return true;
    }

    @Override
    public boolean isAccountNonLocked(a) {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired(a) {
        return true;
    }

    @Override
    public boolean isEnabled(a) {
        return umsAdmin.getStatus().equals(1); }}Copy the code

Add JwtAuthenticationTokenFilter

A filter added before username and password verification, if a valid JWT token is present in the request, extracts the user name from the token and invokes SpringSecurity’s API for login.

package com.macro.mall.tiny.component;

import com.macro.mall.tiny.common.utils.JwtTokenUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

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

/** * Created by macro on 2018/4/26. */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.tokenHeader}")
    private String tokenHeader;
    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader(this.tokenHeader);
        if(authHeader ! =null && authHeader.startsWith(this.tokenHead)) {
            String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
            String username = jwtTokenUtil.getUserNameFromToken(authToken);
            LOGGER.info("checking username:{}", username);
            if(username ! =null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    LOGGER.info("authenticated user:{}", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); }}Copy the code

Project source code address

Github.com/macrozheng/…

The public,

Mall project full set of learning tutorials serialized, attention to the public number the first time access.