This is the seventh day of my participation in the More text Challenge. For details, see more text Challenge

preface

This article focuses on how to integrate SpringSecurity into SpringBoot

Past the link

  • Brief account main function introduction
  • Brief introduction and deployment of the accounting backend environment
  • Brief introduction and deployment of account front end environment
  • Solve the small program scan code authorization prompt Token cannot be empty
  • Database design
  • Resolve AOP logging errors
  • Brief Spring Security

I. Development environment

The development environment is as follows:

  • Java: its 11
  • Spring Security: 5.3.3

Integrate Spring Security

This section uses SpringSecurity + JWT as an example

1. Introduce dependencies

Spring-security does not need to specify a version, it is specified in the parent POM. The JJWT library also needs to be introduced because JWT is required

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<! -- JJWT is a Java library that provides end-to-end JWT creation and validation -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.7.0</version>
</dependency>
Copy the code

2. Configure Spring Security

The master configuration

The main configuration declares the requests that need to be intercepted and defines the Filter

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsServiceImpl userDetailsService;


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

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

    / * * * anyRequest | match all the access request path * | SpringEl expression results when they can access to true * anonymous | anonymous access to * denyAll | users cannot access * FullyAuthenticated | user authentication can access fully (not remember - automatic login under me) * hasAnyAuthority | if you have parameters, parametric representation rights, have access any one can access * hasAnyRole | if there is a parameter, Parameters according to role, then any one role can access * hasAuthority | if you have parameters, parameter access, is it can access * hasIpAddress | if you have parameters, parameter indicates the IP address, if the user IP and matching parameters, Can access * hasRole | if you have parameters, parameter, Its role can access * permitAll | users can access any * rememberMe | allow authenticated by remember - me login user access * | accessible * / after the user login
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                // No session is required
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers("/user/register")
                .antMatchers("/doc.html")
                .antMatchers("/webjars/**")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/v2/**")
                .antMatchers("/favicon.ico")
                // the Url obtained by wechat openId is not intercepted
                .antMatchers("/wx/openId/*")
                .antMatchers("/wx/login")
                // The login qr code is not blocked
                .antMatchers("/qrcode/**");
        web.expressionHandler(new DefaultWebSecurityExpressionHandler() {
            @Override
            protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
                WebSecurityExpressionRoot root = (WebSecurityExpressionRoot) super.createSecurityExpressionRoot(authentication, fi);
                root.setDefaultRolePrefix(""); // remove the prefix ROLE_
                returnroot; }}); }@Bean
    CorsConfigurationSource corsConfigurationSource(a) {
        return req -> {
            CorsConfiguration cfg = new CorsConfiguration();
            cfg.addAllowedHeader("*");
            cfg.addAllowedMethod("*");
            cfg.addAllowedOrigin("*");
            cfg.setAllowCredentials(true);
            cfg.checkOrigin("*");
            returncfg; }; }}Copy the code

The authentication

The main thing in the authentication filter is to get the user name and password from the request and give it to the AuthenticationManager

@Slf4j
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/user/login");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // Get the login information from the input stream
        try {
            LoginVO loginVO = new ObjectMapper().readValue(request.getInputStream(), LoginVO.class);
            return authenticationManager.authenticate(
                    // The password must be encrypted for authentication
                    new UsernamePasswordAuthenticationToken(loginVO.getUsername(), loginVO.getPassword())
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null; }}/** * Successfully called method */
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        JWTUser jwtUser = (JWTUser) authResult.getPrincipal();
        log.info("JWTUser:{}",jwtUser.toString());
        List<String> permissionList = new ArrayList<>();
        jwtUser.getAuthorities().forEach(n -> permissionList.add(n.getAuthority()));
        // Get the JWTConfig object by getting the Spring context
        JWTConfig jwtConfig = SpringContextUtil.getBean(JWTConfig.class);
        String token = jwtConfig.createToken(jwtUser.getId().toString(),permissionList);
        UserService userService = SpringContextUtil.getBean(UserService.class);
        UserDO userDO = userService.getById(jwtUser.getId());
        LoginSuccessVO vo = new LoginSuccessVO();
        BeanUtils.copyProperties(userDO, vo);
        vo.setToken(token);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(JSON.toJSONString(Result.success(vo)));
    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8"); response.getWriter().write(JSON.toJSONString(Result.error(CodeMsg.LOGIN_ERROR), SerializerFeature.WriteMapNullValue)); }}Copy the code

authorization

Authorization filters do one thing: set Authentication in the context of the SecurityContext

@Slf4j
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 token = request.getHeader("token");
        // Verify the token correctness
        try {
            checkToken(token);
            // If there is a token in the request header, it is resolved and authentication information is set
            SecurityContextHolder.getContext().setAuthentication(getAuthentication(token));
            super.doFilter(request, response, chain);
        } catch (BaseException e) {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8"); response.getWriter().write(JSON.toJSONString(Result.error(e.getCodeMsg(), e.getMessage()), SerializerFeature.WriteMapNullValue)); }}// Get the user information from the token and create a token
    private UsernamePasswordAuthenticationToken getAuthentication(String token) {
        JWTConfig jwtConfig = SpringContextUtil.getBean(JWTConfig.class);
        Claims claims = jwtConfig.getTokenClaim(token);
        if(claims ! =null){
            String userId = jwtConfig.getUserIdFromToken(claims);
            // Set current user Id to thread
            LocalUserId.set(Long.valueOf(userId));
            List<String> permissions = jwtConfig.getPermissions(claims);
            List<GrantedAuthority> list = new ArrayList<>();
            permissions.forEach(
                    n-> list.add(new SimpleGrantedAuthority(n))
            );
            return new UsernamePasswordAuthenticationToken(userId, null, list);
        }else {
            log.error("Token format is incorrect");
            throw newBusinessException(CodeMsg.JWT_EXCEPTION); }}/** * Verify token correctness */
    private void checkToken(String token) {
        JWTConfig jwtConfig = SpringContextUtil.getBean(JWTConfig.class);
        if(StringUtils.isEmpty(token)){
            log.error("{} cannot be empty",jwtConfig.getHeader());
            throw new BusinessException(CodeMsg.JWT_EXCEPTION);
        }
        Claims claims = jwtConfig.getTokenClaim(token);
        if(claims == null){
            log.error(CodeMsg.JWT_EXCEPTION.getMessage());
            throw new BusinessException(CodeMsg.JWT_EXCEPTION);
        }
        if (jwtConfig.isTokenExpired(claims)) {
            log.error(CodeMsg.TOKEN_EXPIRED.getMessage());
            throw newParameterException(CodeMsg.TOKEN_EXPIRED); }}}Copy the code

3. Write permission interfaces

Here to charge to an account in the analysis of an interface as an example the @ PreAuthorize (” hasAuthority (‘ record: analysis: spendCategoryTotal ‘) “) on behalf of the current user must have the permission to characters

@LoginRequired
    @commonLog (title = "Get total of all consumer categories for a year ", businessType = Businesstype.query)
    @apiOperation (value = "get the total of all consumption categories for a year ")
    @PreAuthorize("hasAuthority('record:analysis:spendCategoryTotal')")
    @GetMapping("/spendCategoryTotal/{year}/{recordType}")
    publicResult<? > getSpendCategoryTotalInYear(@apiparam (required = true, value = "yyyy ") @Validated
                                                 @DateTimeFormat(pattern="yyyy") @PathVariable(value = "year") Date date,
                                                 @notnull (Message = "Billing type code cannot be empty ") @PathVariable(value = "recordType")String recordType) {
        if(! recordType.equals(RecordConstant.EXPEND_RECORD_TYPE) && ! recordType.equals(RecordConstant.INCOME_RECORD_TYPE)) {return Result.error(CodeMsg.RECORD_TYPE_CODE_ERROR);
        }
        UserDO userDO = LocalUser.get();
        List<SpendCategoryTotalDTO> list = recordDetailService.getSpendSpendCategoryTotalByYear(userDO.getId(), recordType,
                date);
        return Result.success(list);
    }
Copy the code

4. Test

The results can be successfully obtained

Third, summary

The above code can be found in the ledger back end

Thank you for seeing the end, it is my great honor to help you ~♥