A preface

Hi, I’m a Knowledge seeker and this is the fourth article in springSecurity. For those of you who don’t have the basic knowledge, please come back to this article after studying. Source code address attached at the end of the article;

The pom

The poM file introduces the dependency that the security initiator supports the security function; Lombok simplifies development; Fastjson for Json processing;

JJWT for JWT token support; Lang3 string processing;

 <dependencies>
     <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>
        <! -- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
</dependencies>
Copy the code

Iii. Certification Process

  • SecurityContextHolderTo provideSecurityContextAccess permission to.
  • SecurityContext, saveAuthenticationAnd possibly request specific security information.
  • AuthenticationRepresents validation in a Spring Security-specific manner.
  • GrantedAuthorityTo reflect the application-wide permissions granted to the principal.
  • UserDetailsProvides the information needed to build an Authentication object from the application’s DAO or other secure data source.
  • UserDetailsService, based on theStringIs created when the user name (or certificate ID, etc.) is passedUserDetails.

Get the user information from the data source and assemble it into the UserDetails, then pass the UserDetailsService the UserDetails. The SecurityContextHolder stores the entire user context and stores Authentication through the SecurityContext, thus ensuring that springSecurity holds the user information;

Four entities

SysUser implements UserDetails to store user information, mainly the user name, password, and permission.

/ * * *@Author lsc
 * <p> </p>
 */
@Data
public class SysUser implements UserDetails {

    / / user name
    private String username;
    / / password
    private String password;
    // Permission information
    private Set<? extends GrantedAuthority> authorities;

    @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 true; }}Copy the code

Five Token utility classes

The token tool class is mainly used to produce, parse and verify tokens. What needs to be noted here is that the permission is merged into the step of generating toEKN, so that the permission can be obtained through token, and the permission information can be obtained through token during the permission verification. Disadvantages on the authorization after the token is not updated will cause permissions not synchronized;

/ * * *@Author lsc
 * <p> </p>
 */
public class JwtUtil {

    private static final String CLAIMS_ROLE = "zszxzRoles";

    /** * 5 days */
    private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 5;
    /** * JWT password */
    private static final String SECRET = "secret";


    /** * issue JWT */
    public static String getToken(String username, String roles) {
        Map<String, Object> claims = new HashMap<>(8);
        / / the main body
        claims.put( CLAIMS_ROLE, roles);
        return Jwts.builder()
                .setClaims(claims)
                .claim("username",username)
                .setExpiration( new Date( Instant.now().toEpochMilli() + EXPIRATION_TIME  ) )// Expiration time
                .signWith( SignatureAlgorithm.HS512, SECRET )/ / encryption
                .compact();
    }

    /** * verify JWT */
    public static Boolean validateToken(String token) {
        return(! isTokenExpired( token )); }/** * Whether the obtained token expires */
    public static Boolean isTokenExpired(String token) {
        Date expiration = getExpireTime( token );
        return expiration.before( new Date() );
    }

    /** * Get username */ based on token
    public static String getUsernameByToken(String token) {
        String username = (String) parseToken( token ).get("username");
        return username;
    }

    public static Set<GrantedAuthority> getRolseByToken(String token) {
        String rolse = (String) parseToken( token ).get(CLAIMS_ROLE);
        String[] strArray = StringUtils.strip(rolse, "[]").split(",");
        Set<GrantedAuthority> authoritiesSet = new HashSet();
        if (strArray.length>0){
            Arrays.stream(strArray).forEach(rols-> {
                GrantedAuthority authority = new SimpleGrantedAuthority(rols);
                authoritiesSet.add(authority);
            });
        }
        return authoritiesSet;
    }

    /** * The expiration time of the token */
    public static Date getExpireTime(String token) {
        Date expiration = parseToken( token ).getExpiration();
        return expiration;
    }

    /** * parse JWT */
    private static Claims parseToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey( SECRET )
                .parseClaimsJws( token )
                .getBody();
        returnclaims; }}Copy the code

Six UserDetailsService

UserDetailsService The user queries the database data, encapsulates the user data to UserDetails, and goes this way during user identity authentication. The PasswordEncoder provided by the official is used for encryption. The configuration mode needs to be configured in WebSecurityConfig.

/ * * *@Author lsc
 * <p> </p>
 */
@Component
@Slf4j
public class SysUserDetailsService implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    // During login authentication, obtain all permission information of the user through username. In the formal environment, this is the authorization for querying user data
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("------ user {} authentication -----",username);
        // Create a user
        SysUser user = new SysUser();
        / / account
        user.setUsername(username);
        / / password
        user.setPassword(passwordEncoder.encode("123456"));
        // Set permissions
        Set authoritiesSet = new HashSet();
        // Notice Role permissions must be prefixed with ROLE_. Otherwise, 403 is reported
        GrantedAuthority userPower = new SimpleGrantedAuthority("ROLE_USER");
        GrantedAuthority adminPower = new SimpleGrantedAuthority("ROLE_ADMIN");
        authoritiesSet.add(userPower);
        authoritiesSet.add(adminPower);
        user.setAuthorities(authoritiesSet);
        returnuser; }}Copy the code

Seven JWTLoginFilter

JWTLoginFilter inheritance AbstractAuthenticationProcessingFilter filter; Inheritance UsernamePasswordAuthenticationFilter is feasible in theory, It is, after all UsernamePasswordAuthenticationFilter AbstractAuthenticationProcessingFilter implementation class;

JWTLoginFilter is used for user login authentication, which implements the following three methods:

  • AttemptAuthentication is used to attemptAuthentication, and if authentication is successful, the successfulAuthentication method is used. If authentication failure can walk unsuccessfulAuthentication method;
  • After successfulAuthentication, you need to generate a token, which is returned to the front end as JSON.
  • UnsuccessfulAuthentication authentication failure, we determine from the abnormal information then return an error message to the front;
/ * * *@AuthorLSC * < P > Login authentication filter </p> */
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {


    public JWTLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
        super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
        setAuthenticationManager(authenticationManager);
    }


    / * * *@AuthorLSC * < P > Login authentication </p> *@Param [request, response]
     * @Return* /
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SysUser user = new ObjectMapper().readValue(request.getInputStream(), SysUser.class);
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                user.getUsername(),
                user.getPassword());
        return getAuthenticationManager().authenticate(authenticationToken);
    }

    / * * *@AuthorLSC * <p> Logon successfully returns token</p> *@Param [request, res, chain, auth]
     * @Return* /
        @Override
        protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,FilterChain chain, Authentication auth){
            SysUser principal = (SysUser)auth.getPrincipal();
            String token = JwtUtil.getToken(principal.getUsername(),principal.getAuthorities().toString());
            try {
                // If the login succeeds, a message is displayed in json format
                response.setContentType("application/json; charset=utf-8");
                response.setStatus(HttpServletResponse.SC_OK);
                PrintWriter out = response.getWriter();
                ResultPage result = ResultPage.sucess(CodeMsg.SUCESS,token);
                out.write(new ObjectMapper().writeValueAsString(result));
                out.flush();
                out.close();
            } catch(Exception e1) { e1.printStackTrace(); }}@Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        String result="";
        // The account has expired
        if (failed instanceof AccountExpiredException) {
            result="Account expired";
        }
        // The password is incorrect
        else if (failed instanceof BadCredentialsException) {
            result="Password error";
        }
        // The password has expired
        else if (failed instanceof CredentialsExpiredException) {
            result="Password expired";
        }
        // The account is unavailable
        else if (failed instanceof DisabledException) {
            result="Account is not available";
        }
        // The account is locked
        else if (failed instanceof LockedException) {
            result="Account locked";
        }
        // The user does not exist
        else if (failed instanceof InternalAuthenticationServiceException) {
            result="User does not exist";
        }
        // Other errors
        else{
            result="Unknown exception";
        }
        // Process encoding mode to prevent Chinese garbled characters
        response.setContentType("text/json; charset=utf-8");
        // Stuff the feedback back to the foreground in HttpServletResponseresponse.getWriter().write(JSON.toJSONString(result)); }}Copy the code

Eight WebSecurityConfig

WebSecurityConfig is the configuration information for springSecurity; You can configure data access permission restriction, authorization exception handling, and account encryption mode.

/ * * *@Author lsc
 * <p> </p>
 */
@EnableWebSecurity/ / open springSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    DenyHandler denyHandler;

    @Autowired
    OutSuccessHandler outSuccessHandler;

    @Autowired
    SysUserDetailsService userDetailsService;

    @Autowired
    ExpAuthenticationEntryPoint expAuthenticationEntryPoint;

    / * * * @ Author LSC * authorized < / p > < p > * @ Param [HTTP] * /
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()/ / authorization
                .antMatchers("/api/download/**").anonymous()// Anonymous user permissions
                .antMatchers("/api/**").hasRole("USER")// Common user permission
                .antMatchers("/api/admin/**").hasRole("ADMIN")// Admin permission
                .antMatchers("/login").permitAll()
                // Others require authorization after access
                .anyRequest().authenticated()
                .and()/ / exception
                .exceptionHandling()
                .accessDeniedHandler(denyHandler)// Authorize exception handling
                .authenticationEntryPoint(expAuthenticationEntryPoint)// Authentication exception handling
                .and()
                .logout()
                .logoutSuccessHandler(outSuccessHandler)
                .and()
                .addFilterBefore(new JWTLoginFilter("/login",authenticationManager()),UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JwtAuthenticationFilter(authenticationManager()),UsernamePasswordAuthenticationFilter.class)
                .sessionManagement()
                // Set the Session creation policy to: Spring Security does not create httpSessions
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable();// Close CSRF otherwise post


    }



    /* * * @author LSC * 

Authentication set encryption mode

* @param [auth] */
@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder(a){ return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManager(a) throws Exception { return super.authenticationManager(); }}Copy the code

Nine Handler

Configuration used in the three classes, respectively is denyHandler, outSuccessHandler, expAuthenticationEntryPoint;

DenyHandler this class is used to verify permissions if there are insufficient permissions

/ * * *@AuthorLSC * <p> Insufficient permission handling </p> */
@Component
public class DenyHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        // Set the response header
        httpServletResponse.setContentType("application/json; charset=utf-8");
        / / the return valueResultPage result = ResultPage.error(CodeMsg.PERM_ERROR); httpServletResponse.getWriter().write(JSON.toJSONString(result)); }}Copy the code

OutSuccessHandler (localhost:8080/logout);

/ * * *@Author lsc
 * <p> </p>
 */
@Component
public class OutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        // Set the response header
        httpServletResponse.setContentType("application/json; charset=utf-8");
        / / the return value
        ResultPage result = ResultPage.sucess(CodeMsg.SUCESS,"Logout successful."); httpServletResponse.getWriter().write(JSON.toJSONString(result)); }}Copy the code

ExpAuthenticationEntryPoint is responsible for the identity authentication by exception handling, after every major authentication system has its own AuthenticationEntryPoint implementation;

/ * * *@Author lsc
 * <p> </p>
 */
@Component
public class ExpAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        // Set the response header
        httpServletResponse.setContentType("application/json; charset=utf-8");
        / / the return valueResultPage result = ResultPage.error(CodeMsg.ACCOUNT_ERROR); httpServletResponse.getWriter().write(JSON.toJSONString(result)); }}Copy the code

Ten Controller

SysUserController is used to provide permission tests

/ * * *@Author lsc
 * <p> </p>
 */
@RestController
public class SysUserController {


    @GetMapping("api/admin")
    @PreAuthorize("hasAuthority('ADMIN')")
    public String authAdmin(a) {
        return "ADMIN permission required";
    }

    @GetMapping("api/test")
    @PreAuthorize("hasAuthority('USER')")
    public String authUser(a) {
        return "USER permission required"; }}Copy the code

The overall project structure is as follows

11 test

The user logs in and returns the token

Request interface test, return data

User exit returns a message.

The last

Reference documentation

Blog.csdn.net/Piconjo/art…

www.jianshu.com/p/8bd4a6e27…

www.jianshu.com/p/bd882078f…

Docs. Spring. IO/spring – secu…

Source code address: welcome to pay attention to the public number: knowledge seeker, background reply SpringSecurity this set of tutorials:

  • SpringSecurity introduction (1)
  • SpringSecurity configuration (2)
  • Json interaction between springSecurity login and exit (3)
  • SpringSecurity back-end separation integration JWT (4)
  • To be continued or updated