Youth in violation

Today I wrote a simple Demo of Spring Security integration with JWT. The purpose is to return tokens after login. The whole process is actually very simple.

Import dependence

<dependency>

    <groupId>io.jsonwebtoken</groupId>

    <artifactId>jjwt</artifactId>

    <version>0.9.0</version>

</dependency>

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-security</artifactId>

</dependency>

Copy the code

Start by creating a JwtUser implementation of UserDetails

Org. Springframework. Security. Core. Populated userdetails. Populated userdetails first have a look at the interface of the source code, actually very simple

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();



    String getPassword(a);



    String getUsername(a);



    boolean isAccountNonExpired(a);



    boolean isAccountNonLocked(a);



    boolean isCredentialsNonExpired(a);



    boolean isEnabled(a);

}

Copy the code

This is a simple interface provided by Spring Security, because we need to obtain user credentials and other information through the SecurityContextHolder. Because this is relatively simple, we need to add the information we need for actual business.

public class JwtUser implements UserDetails {





    private String username;



    private String password;



    private Integer state;



    private Collection<? extends GrantedAuthority> authorities;



    public JwtUser(a) {

    }



    public JwtUser(String username, String password, Integer state, Collection<? extends GrantedAuthority> authorities) {

        this.username = username;

        this.password = password;

        this.state = state;

        this.authorities = authorities;

    }



    @Override

    public String getUsername(a) {

        return username;

    }



    @JsonIgnore

    @Override

    public String getPassword(a) {

        return password;

    }



    @Override

    public Collection<? extends GrantedAuthority> getAuthorities() {

        return authorities;

    }



    // Whether the account has not expired

    @JsonIgnore

    @Override

    public boolean isAccountNonExpired(a) {

        return true;

    }



    // Whether the account is unlocked

    @Override

    public boolean isAccountNonLocked(a) {

        return true;

    }







    @JsonIgnore

    @Override

    public boolean isCredentialsNonExpired(a) {

        return true;

    }





    // Whether to enable it

    @JsonIgnore

    @Override

    public boolean isEnabled(a) {

        return true;

    }

}

Copy the code

This is actually very simple, just a set of user names, password status and permissions.

Write a utility class to generate tokens, etc…

@Data

@ConfigurationProperties(prefix = "jwt")

@Component

public class JwtTokenUtil implements Serializable {



    private String secret;



    private Long expiration;



    private String header;



    / * *

* Generate tokens from data declarations

     *

     * @paramClaims Data Declaration

     * @returnThe token

* /


    private String generateToken(Map<String, Object> claims) {

        Date expirationDate = new Date(System.currentTimeMillis() + expiration);

        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();

    }



    / * *

* Get the data declaration from the token

     *

     * @paramToken token

     * @returnData declaration

* /


    private Claims getClaimsFromToken(String token) {

        Claims claims;

        try {

            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();

        } catch (Exception e) {

            claims = null;

        }

        return claims;

    }



    / * *

* Generate tokens

     *

     * @paramPopulated userDetails user

     * @returnThe token

* /


    public String generateToken(UserDetails userDetails) {

        Map<String, Object> claims = new HashMap<>(2);

        claims.put("sub", userDetails.getUsername());

        claims.put("created".new Date());

        return generateToken(claims);

    }



    / * *

* Get the user name from the token

     *

     * @paramToken token

     * @returnThe user name

* /


    public String getUsernameFromToken(String token) {

        String username;

        try {

            Claims claims = getClaimsFromToken(token);

            username = claims.getSubject();

        } catch (Exception e) {

            username = null;

        }

        return username;

    }



    / * *

* Determine whether the token is expired

     *

     * @paramToken token

     * @returnIs late

* /


    public Boolean isTokenExpired(String token) {

        try {

            Claims claims = getClaimsFromToken(token);

            Date expiration = claims.getExpiration();

            return expiration.before(new Date());

        } catch (Exception e) {

            return false;

        }

    }



    / * *

* Refresh the token

     *

     * @paramToken the token

     * @returnThe new token

* /


    public String refreshToken(String token) {

        String refreshedToken;

        try {

            Claims claims = getClaimsFromToken(token);

            claims.put("created".new Date());

            refreshedToken = generateToken(claims);

        } catch (Exception e) {

            refreshedToken = null;

        }

        return refreshedToken;

    }



    / * *

* Validate the token

     *

     * @paramToken token

     * @paramPopulated userDetails user

     * @returnThe validity of

* /


    public Boolean validateToken(String token, UserDetails userDetails) {

        JwtUser user = (JwtUser) userDetails;

        String username = getUsernameFromToken(token);

        return(username.equals(user.getUsername()) && ! isTokenExpired(token));

    }

}

Copy the code

This class is all about token generation, validation, and so on. See notes ~

Write a Filter

@Slf4j

@Component

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {







    @Autowired

    private UserDetailsService userDetailsService;



    @Autowired

    private JwtTokenUtil jwtTokenUtil;





    @Override

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        String authHeader = request.getHeader(jwtTokenUtil.getHeader());

        if(authHeader ! =null && StringUtils.isNotEmpty(authHeader)) {

            String username = jwtTokenUtil.getUsernameFromToken(authHeader);

            log.info(username);

            if(username ! =null && SecurityContextHolder.getContext().getAuthentication() == null) {

                UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

                if (jwtTokenUtil.validateToken(authHeader, userDetails)) {

                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    SecurityContextHolder.getContext().setAuthentication(authentication);

                }

            }

        }

        chain.doFilter(request, response);

    }

}

Copy the code

This is actually used to verify whether the token is valid. Since today’s Demo is a simple login return token process, this default does not execute the logic inside. However, the logics will be executed after login.

JwtUserDetailsServiceImpl

JwtUserDetailsServiceImpl is the implementation class implements UserDetailsService UserDetailsService is Spring Security will be used for authentication, we have here is a simple method to load user information, Is to get the current login user some user name, password, the user has a role and so on some information

@Slf4j

@Service

public class JwtUserDetailsServiceImpl implements UserDetailsService {







    @Autowired

    private UserMapper userMapper;

    @Override

    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {



        User user = userMapper.selectByUserName(s);

        if(user == null) {

            throw new UsernameNotFoundException(String.format("'%s'. This user does not exist", s));

        }

        List<SimpleGrantedAuthority> collect = user.getRoles().stream().map(Role::getRolename).map(SimpleGrantedAuthority::new).collect(Collectors.toList());

        return new JwtUser(user.getUsername(), user.getPassword(), user.getState(), collect);

    }

}

Copy the code

Write the business implementation class for the login

@Slf4j

@Service

public class UserServiceImpl implements UserService {

    @Autowired

    private UserMapper userMapper;







    @Autowired

    private AuthenticationManager authenticationManager;



    @Autowired

    private UserDetailsService userDetailsService;



    @Autowired

    private JwtTokenUtil jwtTokenUtil;





    public User findByUsername(String username) {

        User user = userMapper.selectByUserName(username);

        log.info("userserviceimpl"+user);

        return user;

    }



    public RetResult login(String username, String password) throws AuthenticationException {

        UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(username, password);

        final Authentication authentication = authenticationManager.authenticate(upToken);

        SecurityContextHolder.getContext().setAuthentication(authentication);

        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        return new RetResult(RetCode.SUCCESS.getCode(),jwtTokenUtil.generateToken(userDetails));

    }

}

Copy the code

As you can see from above, the login method will then return a token to us based on the user information.

WebSecurityConfig

This is the Configuration class for Spring Security

@EnableWebSecurity

@EnableGlobalMethodSecurity(prePostEnabled = true)

public class WebSecurity extends WebSecurityConfigurerAdapter {



    @Autowired

    private UserDetailsService userDetailsService;



    @Autowired

    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;



    @Autowired

    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder)throws Exception{

        authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());

    }





    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)

    @Override

    public AuthenticationManager authenticationManagerBean(a) throws Exception {

        return super.authenticationManagerBean();

    }



    @Bean

    public PasswordEncoder passwordEncoder(a) {

        return new BCryptPasswordEncoder();

    }



    @Override

    protected void configure(HttpSecurity http) throws Exception {



        http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                .and().authorizeRequests()

                .antMatchers(HttpMethod.OPTIONS, "/ * *").permitAll()

                .antMatchers("/auth/**").permitAll()

                .anyRequest().authenticated()

                .and().headers().cacheControl();



        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);





        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();

        // Let Spring Security pass all preFlight requests

        registry.requestMatchers(CorsUtils::isPreFlightRequest).permitAll();

    }



    @Bean

    public CorsFilter corsFilter(a) {

        final UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();

        final CorsConfiguration cors = new CorsConfiguration();

        cors.setAllowCredentials(true);

        cors.addAllowedOrigin("*");

        cors.addAllowedHeader("*");

        cors.addAllowedMethod("*");

        urlBasedCorsConfigurationSource.registerCorsConfiguration("/ * *", cors);

        return new CorsFilter(urlBasedCorsConfigurationSource);

    }

}

Copy the code

We can set custom interception rules here. Note that in Spring Security5.x we need to explicitly inject AuthenticationManager or we will get an error

@Bean(name = BeanIds.AUTHENTICATION_MANAGER)

    @Override

    public AuthenticationManager authenticationManagerBean(a) throws Exception {

        return super.authenticationManagerBean();

    }

Copy the code

At present, the basic information has been completed, and the rest is some entity and controller codes. You can see the codes on GitHub for details. If you have any comments or any mistakes I made, please contact me… I will definitely change…