Write before:
Recently, I have an idea to be a programmer apprentice management system. Because I was very confused when learning java in college, I couldn't find my own direction, and I didn't have an experienced senior in the society to guide, so I took a lot of detours. Later, I worked and wanted to share my pit avoidance experience with others, but I found that I was surrounded by experienced developers and had no opportunity to share my ideas. Therefore, rich students wanted to be a programmer's exclusive apprenticeship system, adhering to the purpose that apprentices can be taught by others, avoid detours, and masters can enrich the world, so I began to do this apprenticeship system, The technology used in the system will also be updated synchronously and shared with you as a tutorial. I hope you can pay attention to a wave.
OK, let's talk about the technology used to change the security module in the system: JWT. Integrate JWT with SpringBoot.
The first step is to import the jar package
<!--JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency><!--solve: java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter --> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.0</version> </dependency> <!--JWT-->
We use jjwt here. Why not use the native jwt? Let's meet jjwt:
JJWT can be understood as the framework of jwt
-
JJWT is a Java library that provides end-to-end JWT creation and validation. Always free and open source (Apache License, version 2.0), JJWT is easy to use and understand. It is designed as a smooth interface centered on architecture, hiding most of its complexity.
-
The goal of JJWT is to make it easiest to use and understand the library used to create and validate JSON Web tokens (JWTs) on the JVM.
-
JJWT is a Java implementation based on JWT, JWS, JWE, JWK and JWA RFC specifications.
-
JJWT also adds some convenient extensions that are not part of the specification, such as JWT compression and claim enforcement.
When you guide the package, remember to import the JAXB API package, otherwise you will report an error when creating the jwt token, which is a pit trodden down by rich and noble students according to the online tutorial.
Next, we need spring security as a security framework to use jwt, so we also need to import the package of spring security:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
The second step is to write our tool class to manage jwt token
public class JwtTokenUtils { public static final String TOKEN_HEADER = "Authorization"; public static final String TOKEN_PREFIX = "Bearer "; private static final String SECRET = "jwt"; private static final String ISS = "echisan"; // key of role private static final String ROLE_CLAIMS = "rol"; // The expiration time is 3600 seconds, which is 1 hour private static final long EXPIRATION = 3600L; // The expiration time after remembering me is 7 days private static final long EXPIRATION_REMEMBER = 604800L; // Create token public static String createToken(String username, String role, boolean isRememberMe) { long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION; HashMap<String, Object> map = new HashMap<>(); map.put(ROLE_CLAIMS, role); return Jwts.builder() .signWith(SignatureAlgorithm.HS256, SECRET.getBytes(StandardCharsets.UTF_8)) .setClaims(map) .setIssuer(ISS) .setSubject(username) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .compact(); } // Get user name from token public static String getUsername(String token) { return getTokenBody(token).getSubject(); } // Get user role public static String getUserRole(String token) { return (String) getTokenBody(token).get(ROLE_CLAIMS); } // Is it expired public static boolean isExpiration(String token) { try { return getTokenBody(token).getExpiration().before(new Date()); } catch (ExpiredJwtException e) { return true; } } private static Claims getTokenBody(String token) { return Jwts.parser() .setSigningKey(SECRET.getBytes(StandardCharsets.UTF_8)) .parseClaimsJws(token) .getBody(); } }
This class is actually very simple, but it is to generate token s and obtain user information.
You should pay attention here. Because jwt is a string that can store user information through special coding, you should not save sensitive information in jwt. How? Look at the createToken () method in the tool class above. For example, the setSubject () method can store the user's name in the jwt string. These can be decompiled through the anti coding technology, so please note.
Now that we have a tool class, we need to use jwt instead of springsecurity to manage users' token s, so we should think of configuring jwt's filter class in the WebSecurityConfigurerAdapter class:
@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomUserDetailsService userDatailService; /** * anyRequest | Match all request paths * access | SpringEl Can be accessed when the expression result is true * anonymous | Anonymous access * denyAll | User cannot access * fullyAuthenticated | The user can be accessed through full authentication (automatic login under non remember me) * hasAnyAuthority | If there are parameters, and the parameters represent permissions, any one of them can be accessed * hasAnyRole | If there are parameters, and the parameters represent roles, any of them can be accessed * hasAuthority | If there are parameters and the parameters represent permissions, their permissions can be accessed * hasIpAddress | If there are parameters, the parameters represent the IP address. If the user IP and parameters match, they can be accessed * hasRole | If there are parameters, and the parameters represent roles, their roles can be accessed * permitAll | Users can access it at will * rememberMe | Allow users logged in through remember me to access * authenticated | Users can access after logging in */ @Override protected void configure(HttpSecurity http) throws Exception { // Close csrf verification for post request, or an error will be reported for access; When it is started in actual development, it needs the cooperation of the front end to transfer other parameters http.csrf().disable() .authorizeRequests() .antMatchers("/swagger-ui.html").anonymous() .antMatchers("/swagger-resources/**").anonymous() .antMatchers("/webjars/**").anonymous() .antMatchers("/*/api-docs").anonymous() //Release the registration and log in to the user interface .antMatchers("/user/register").anonymous() .antMatchers("/user/login").anonymous() .anyRequest().authenticated() // All requests require validation .and() .formLogin() // Use default login page .and() //Add authentication of user account .addFilter(new JWTAuthenticationFilter(authenticationManager())) //Add authentication for user rights .addFilter(new JWTAuthorizationFilter(authenticationManager())) .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .exceptionHandling() //Add an operation that does not carry a token or the token is invalid .authenticationEntryPoint(new JWTAuthenticationEntryPoint()) //Add processing for unauthorized time limit .accessDeniedHandler(new JWTAccessDeniedHandler()); } /** * Specify encryption method */ @Bean public PasswordEncoder passwordEncoder() { // Encrypt password using BCrypt return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth // Authenticate users read from the database .userDetailsService(userDatailService) .passwordEncoder(passwordEncoder()); } // @Override // protected void configure(HttpSecurity httpSecurity) throws Exception{ // //Commissioning phase // httpSecurity.csrf().disable().authorizeRequests(); // httpSecurity.authorizeRequests().anyRequest() // .permitAll().and().logout().permitAll(); // } }
In this method:
//Add authentication of user account .addFilter(new JWTAuthenticationFilter(authenticationManager())) //Add authentication for user rights .addFilter(new JWTAuthorizationFilter(authenticationManager()))
After this configuration, you can write jwt's authentication class and authorization class to proxy springsecurity to manage user authentication and authorization:
/** * Verification of user account * JWTAuthenticationFilter Inherited from UsernamePasswordAuthenticationFilter, the interceptor is used to obtain the login information of the user. Just create a token and call * authenticationManager.authenticate()Just let spring security verify it. You don't have to check the database and compare the password. This step is left to spring. * This operation is a bit like shiro's subject.login(new UsernamePasswordToken()) * Created by Mrfugui 2021 10:53:30, October 30 */ public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private ThreadLocal<Integer> rememberMe = new ThreadLocal<>(); private AuthenticationManager authenticationManager; public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; //Special attention should be paid here to the login interface. The user-defined login interface defaults to "/ login" if it is not written super.setFilterProcessesUrl("/auth/login"); } @Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // Get the login information from the input stream try { LoginUser loginUser = new ObjectMapper().readValue(request.getInputStream(), LoginUser.class); rememberMe.set(loginUser.getRememberMe() == null ? 0 : loginUser.getRememberMe()); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginUser.getUsername(), loginUser.getPassword(), new ArrayList<>()) ); } catch (IOException e) { e.printStackTrace(); return null; } } // The method of calling after successful verification // If the verification is successful, a token is generated and returned @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) { JwtUser jwtUser = (JwtUser) authResult.getPrincipal(); System.out.println("jwtUser:" + jwtUser.toString()); boolean isRemember = rememberMe.get() == 1; String role = ""; Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities(); for (GrantedAuthority authority : authorities){ role = authority.getAuthority(); } String token = JwtTokenUtils.createToken(jwtUser.getUsername(), role, isRemember); // Returns the token created successfully // However, the token created here is only a simple token // According to jwt, the last request should be ` Bearer token` response.setHeader("Authorization", JwtTokenUtils.TOKEN_PREFIX + token); } @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { response.getWriter().write(JSONObject.toJSONString(ResponseUtils.msg(failed.getMessage()))); } }
/** * Of course, successful verification means authentication. Every request requiring permission needs to check whether the user has the permission to operate the resource. Of course, this is also done by the framework. What do we need to do? * Very simply, just tell spring security whether the user has logged in, what role it is and what permissions it has. * JWTAuthenticationFilter It inherits from BasicAuthenticationFilter. I don't know why I want to inherit this. This is also one of the implementations I saw on the Internet, * It's really hard for spring security, but I think it's OK not to inherit this (it's OK to implement the following filter interface or inherit other filter implementation subclasses). Just ensure the order of filters, * JWTAuthorizationFilter There is no problem after JWT authentication filter. * Created by MrFugui 2021 October 30, 2013 10:53:44 */ public class JWTAuthorizationFilter extends BasicAuthenticationFilter { public JWTAuthorizationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER); // If there is no Authorization information in the request header, it will be released directly if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) { chain.doFilter(request, response); return; } // If there is a token in the request header, it is parsed and the authentication information is set try { SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader)); } catch (TokenIsExpiredException e) { //Return error information in json form response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.getWriter().write(JSONObject.toJSONString(ResponseUtils.msg(e.getMessage()))); response.getWriter().flush(); return; } super.doFilterInternal(request, response, chain); } // Here, get the user information from the token and create a new token private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException { String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, ""); boolean expiration = JwtTokenUtils.isExpiration(token); if (expiration) { throw new TokenIsExpiredException("token Timeout"); } else { String username = JwtTokenUtils.getUsername(token); String role = JwtTokenUtils.getUserRole(token); if (username != null) { return new UsernamePasswordAuthenticationToken(username, null, Collections.singleton(new SimpleGrantedAuthority(role)) ); } } return null; } }
These two classes are the methods to control the success of user authentication and authorization, but please pay special attention to one point:
//Special attention should be paid here to the login interface. The user-defined login interface defaults to "/ login" if it is not written super.setFilterProcessesUrl("/auth/login");
However, two more authentication and authorization classes are not enough at this time. We also need to deal with the situation that the user does not have a token:
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .exceptionHandling() //Add an operation that does not carry a token or the token is invalid .authenticationEntryPoint(new JWTAuthenticationEntryPoint()) //Add processing for unauthorized time limit .accessDeniedHandler(new JWTAccessDeniedHandler());
The first sentence means:
We can accurately control when to create a session. We have the following options:
//Always – if the session does not exist, it always needs to be created;
//ifRequired – create a session (default configuration) only when required;
//Never – the framework never creates a session, but if it already exists, it will use the session;
//stateless – Spring Security will not create or use session s;
So we also need two classes: JWTAuthenticationEntryPoint and JWTAccessDeniedHandler
Pay attention to their order and don't reverse it:
/** * No token is carried or the token is invalid * @author MrFugui 2021 October 30, 2010 10:55:21 */ public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { String result = ""; response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("application/json;charset=UTF-8"); if (exception instanceof BadCredentialsException || exception instanceof InternalAuthenticationServiceException) { response.setStatus(HttpStatus.BAD_REQUEST.value()); result = JSONObject.toJSONString(ResponseUtils.msg(CodeEnums.PASSWORD_ERROR.getMsg())); } else if (exception instanceof InsufficientAuthenticationException || exception instanceof NonceExpiredException) { result = JSONObject.toJSONString(ResponseUtils.msg(CodeEnums.AUTH_ERROR.getMsg())); } else if (exception instanceof UsernameNotFoundException) { result = JSONObject.toJSONString(ResponseUtils.msg(CodeEnums.NO_USER.getMsg())); } else { result = "The system is abnormal."; } response.getWriter().write(result); } }
/** * @author MrFugui * 2021 October 30, 2014 10:54:20 * @description:No access */ public class JWTAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletResponse.setCharacterEncoding("UTF-8"); httpServletResponse.setContentType("application/json; charset=utf-8"); httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN); httpServletResponse.getWriter().write(JSONObject.toJSONString(ResponseUtils.msg(e.getMessage()))); } }
Well, here, our jwt collection springboot is completed. At this time, we can access our interface after we get the token through the login interface returned by postman
Note: the code in this blog needs to be obtained from the code cloud warehouse, and the warehouse address Gitee-JWT
Say after
I will always update the mentoring system, because it is an open source project, so I hope more small partners will join in!!
This is the address of the programmer apprentice management system:
Programmer apprentice management system