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 tableums_role
: Indicates the background user role listums_permission
: Indicates the background user permission tableums_admin_role_relation
: indicates the relationship table between background users and roles. The relationship between users and roles is many-to-manyums_role_permission_relation
: indicates the relationship between background user roles and permissions. Roles and permissions are many-to-manyums_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.