Spring Boot Access Security Authentication and Authentication

  • 2021-12-11 17:55:40
  • OfStack

Directory Interceptor Authentication

In web application, there are a large number of scenarios that need to be safely calibrated for users. The common practice is to bury it directly into the business code in a hard-coded way. But have you ever thought that this will lead to the code being concise enough (a lot of duplicate code), difficult to maintain when personalized (the access control policies of each business logic are different or even very different), and prone to security leakage (some businesses may not need the current login information, However, the accessed data may be sensitive data that is not protected due to forgetting.

In order to control access security more safely and conveniently, we can think of using springmvc interceptor (HandlerInterceptor), but in fact, we recommend using more mature spring security to complete authentication and authentication.

Interceptor

Interceptor HandlerInterceptor can really help us to complete login interception, or permission verification, or anti-duplicate submission and other requirements. In fact, security control based on url or method level can also be realized based on it.

If you have a relative understanding of the spring mvc request processing flow, its principle is easy to understand.


public interface HandlerInterceptor {
	/**
	 * Intercept the execution of a handler. Called after HandlerMapping determined
	 * an appropriate handler object, but before HandlerAdapter invokes the handler.
	 * 
	 *  Called before the service processor processes the request. Pretreatment, which can be used for coding, security control, authority verification and other processing 
	 * 
	 * handler : controller Within the method, you can pass the HandlerMethod method= ((HandlerMethod)handler); Get to @RequestMapping
	 */	
	boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
 
	/**
	 * Intercept the execution of a handler. Called after HandlerAdapter actually
	 * invoked the handler, but before the DispatcherServlet renders the view.
	 * 
	 *  Execute after the business processor processes the request execution and before generating the view. Post-processing (calling the Service And returns ModelAndView But the page is not rendered, and you have the opportunity to modify ModelAndView
	 */	
	void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;
 
	/**
	 * Callback after completion of request processing, that is, after rendering
	 * the view. Will be called on any outcome of handler execution, thus allows
	 * for proper resource cleanup.
	 * 
	 *  In DispatcherServlet Called after the request has been completely processed, which can be used to clean up resources and so on. Go back to processing (the page has been rendered) 
	 * 
	 */	
	void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}
// You can base on some url Intercept 
@Configuration
public class UserSecurityInterceptor extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        String[] securityUrls = new String[]{"/**"};
        String[] excludeUrls = new String[]{"/**/esb/**", "/**/dictionary/**"};
        registry.addInterceptor(userLoginInterceptor()).excludePathPatterns(excludeUrls).addPathPatterns(securityUrls);
        super.addInterceptors(registry);
    }
 
    /** fixed: url Include in //  Report an error 
     * org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL was not normalized.
     * @return
     */
    @Bean
    public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
        DefaultHttpFirewall firewall = new DefaultHttpFirewall();
        firewall.setAllowUrlEncodedSlash(true);
        return firewall;
    }
 
    @Bean
    public AuthInterceptor userLoginInterceptor() {
        return new AuthInterceptor();
    }
 
    public class AuthInterceptor implements HandlerInterceptor {
        public Logger logger = LoggerFactory.getLogger(AuthInterceptor.class);
        @Autowired
        private ApplicationContext applicationContext; 
        public AuthInterceptor() {
        }
 
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            LoginUserInfo user = null;
            try {
                user = (LoginUserInfo) SSOUserUtils.getCurrentLoginUser();
            } catch (Exception e) {
                logger.error(" From SSO Failed to get user information from login information!   Detailed error message: %s", e);
                throw new ServletException(" From SSO Failed to get user information from login information! ", e);
            }
 
            String[] profiles = applicationContext.getEnvironment().getActiveProfiles();
            if (!Arrays.isNullOrEmpty(profiles)) {
                if ("dev".equals(profiles[0])) {
                    return true;
                }
            }
            if (user == null || UserUtils.ANONYMOUS_ROLE_ID.equals(user.getRoleId())) {
                throw new ServletException(" Failed to get login user information! ");
            }
            return true;
        }
 
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { 
        }
 
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { 
        }
    }
}

Certification

Confirm who the user behind an access request is and what his user information is. There are many ways to authenticate in spring security, the simplest of which is user name and password, LDAP, OpenID, CAS and so on.

In our system, user information needs to be obtained through kxtx-sso module. Authentication through sso is relatively simple, that is, to confirm whether the user logs in through the member system, and package the login information into an authorized object and put it in SecurityContext, which is completed through one filter:


@Data
@EqualsAndHashCode(callSuper = false)
public class SsoAuthentication extends AbstractAuthenticationToken {
    private static final long serialVersionUID = -1799455508626725119L; 
    private LoginUserInfo user; 
    public SsoAuthentication(LoginUserInfo user) {
        super(null);
        this.user = user;
    }
 
    @Override
    public Object getCredentials() {
        return "kxsso";
    }
 
    @Override
    public Object getPrincipal() {
        return user;
    }
 
    @Override
    public String getName() {
        return user.getName();
    }
}
public class SsoAuthenticationProcessingFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        LoginUserInfo user = (LoginUserInfo) SSOUserUtils.getCurrentLoginUser();
        SsoAuthentication auth = new SsoAuthentication(user );
        SecurityContextHolder.getContext().setAuthentication(auth);
        filterChain.doFilter(request, response);
    }
}
@Component
public class SsoAuthenticationProvider implements AuthenticationProvider {
 
    @Value("${env}")
    String env;
 
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        LoginUserInfo loginUserInfo = (LoginUserInfo) authentication.getPrincipal();
        /*
         * DEV The environment allows anonymous users to access, which is convenient for debugging. Other environments must log in. 
         */
        if (!UserUtils.ANONYMOUS_ROLE_ID.equals(loginUserInfo.getRoleId()) || "dev".equals(env)) {
            authentication.setAuthenticated(true);
        } else {
            throw new BadCredentialsException(" Please log in ");
        }
        return authentication;
    }
 
    @Override
    public boolean supports(Class<?> authentication) {
        return SsoAuthentication.class.equals(authentication);
    }
}
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    protected void configure(HttpSecurity http) throws Exception {
        //  Shut down session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and();
 
        //  Allow access to all URL Restrict access in the form of method protection. 
        http.authorizeRequests().anyRequest().permitAll();
 
        //  Registration sso filter
        http.addFilterBefore(ssoAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
    }
 
    @Bean
    SsoAuthenticationProcessingFilter ssoAuthenticationProcessingFilter() {
        return new SsoAuthenticationProcessingFilter();
    }
}

Authentication

Control whether a function can be accessed by the current user, and reject the users who do not meet the requirements. spring security has two main control points:

Based on the request path: controlling a certain 1URL mode must meet certain requirements; Method-based: controlling a certain method must meet certain requirements;

And the forms of control are more diversified:

Code configuration; xml configuration; Annotation control; el expression; Custom access controller;

At present, the requirements of authentication are relatively simple: access is allowed when logging in, and access is prohibited when not logging in. Therefore, one section can be defined to control all Controller that need safety control.

spring security provides a few notes:

@PreAuthorize

控制1个方法是否能够被调用,业务方法(HandlerMethod )的前置处理,比如:

@PreAuthorize("#id<10")限制只能查询Id小于10的用户

@PreAuthorize("principal.username.equals(#username)")限制只能查询自己的信息

@PreAuthorize("#user.name.equals('abc')")限制只能新增用户名称为abc的用户

@PostAuthorize

业务方法调用完之后进行权限检查,后置处理,比如:

@PostAuthorize("returnObject.id%2==0")

public User find(int id) {}

返回值的id是偶数则表示校验通过,否则表示校验失败,将抛出AccessDeniedException

@PreFilter

对集合类型的参数进行过滤,比如:

对集合ids中id不为偶数的进行移除 @PreFilter(filterTarget="ids", value="filterObject%2==0") public void delete(List<Integer> ids, List<String> usernames) {}

@PostFilter

对集合类型的返回值进行过滤,比如:

将对返回结果中id不为偶数的list中的对象进行移除

@PostFilter("filterObject.id%2==0") public List<User> findAll() {}

@AuthenticationPrincipal 解决在业务方法内对当前用户信息的方法

@Aspect
@Component
public class InControllerAspect {
    @Autowired
    BeforeInControllerMethods beforeInMethods;
 
    @Pointcut("execution(public * com.kxtx.oms.portal.controller.in.*.*(..)) && @annotation(org.springframework.web.bind.annotation.RequestMapping)")
    public void hp() {
    };
 
    @Before("hp()")
    public void befor() {
        beforeInMethods.before();
    }
}
@Component
public class BeforeInControllerMethods {
    //@PreAuthorize("authenticated") All users accessing this method are required to log in 
    @PreAuthorize("authenticated")
    public void before() {
    }
}
// User information acquisition 
@RequestMapping("/order/submit")
public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) {
    // .. find messages for this user and return them ...
}

Is it a little complicated? What is complicated is the manifestation. In fact, it is necessary to really understand its purpose (in order to solve what problems).

References

mvc-authentication-principal


Related articles: