Spring Security and JSON Authentication

前端 未结 8 1587
我在风中等你
我在风中等你 2020-11-28 05:11

I\'ve an application in spring/spring-mvc that totally uses JSON communications. Now I need to authenticate my application with spring security 3 (that uses LdapAuthenticati

相关标签:
8条回答
  • 2020-11-28 05:35
    public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
            if (!request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
            }
    
            LoginRequest loginRequest = this.getLoginRequest(request);
    
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
    
            setDetails(request, authRequest);
    
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
        private LoginRequest getLoginRequest(HttpServletRequest request) {
            BufferedReader reader = null;
            LoginRequest loginRequest = null;
            try {
                reader = request.getReader();
                Gson gson = new Gson();
                loginRequest = gson.fromJson(reader, LoginRequest.class);
            } catch (IOException ex) {
                Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                try {
                    reader.close();
                } catch (IOException ex) {
                    Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
    
            if (loginRequest == null) {
                loginRequest = new LoginRequest();
            }
    
            return loginRequest;
        }
    }
    
    0 讨论(0)
  • 2020-11-28 05:37

    If you want just different request body parser for login request just extend UsernamePasswordAuthenticationFilter and override attemptAuthentication method. By default UsernamePasswordAuthenticationFilter will parse url encoded data and create UsernamePasswordAuthenticationToken from it. Now you just need to make parser that will parse whatever you send to application.

    Here is example that will parse {"username": "someusername", "password": "somepassword"}

    public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            try {
                BufferedReader reader = request.getReader();
                StringBuffer sb = new StringBuffer();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line);
                }
                String parsedReq = sb.toString();
                if (parsedReq != null) {
                    ObjectMapper mapper = new ObjectMapper();
                    AuthReq authReq = mapper.readValue(parsedReq, AuthReq.class);
                    return new UsernamePasswordAuthenticationToken(authReq.getUsername(), authReq.getPassword());
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
                throw new InternalAuthenticationServiceException("Failed to parse authentication request body");
            }
            return null;
        }
    
        @Data
        public static class AuthReq {
            String username;
            String password;
        }
    
    }
    

    In snippet request body is extracted to string and mapped to object AuthReq (@Data annotation is from lombok lib, it will generate seters and getters). Than you can make UsernamePasswordAuthenticationToken that will be passed to default AuthenticationProvider.

    Now you can extend WebSecurityConfigurerAdapter and override cnofigure method to replace old filter.

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/", "/login", "/logout").permitAll()
                .anyRequest().authenticated()
            .and().addFilterAt(new CustomUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .formLogin().loginProcessingUrl("/login")
            .and()
            .csrf().disable();
    }
    

    With addFilterAt method you replace default UsernamePasswordAuthenticationFilter. Dont forget to use @EnableWebSecurity annotation.

    0 讨论(0)
  • 2020-11-28 05:37

    Another way, according with this post, is to manage manually the spring security authentication directly in the Controller.
    In this manner is very simple to manage JSON input and avoid login redirect:

    @Autowired
    AuthenticationManager authenticationManager;
    
    @ResponseBody
    @RequestMapping(value="/login.json", method = RequestMethod.POST)
    public JsonResponse mosLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
        JsonResponse response = null;
    
        try {
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword());
            token.setDetails(new WebAuthenticationDetails(request));
    
            Authentication auth = authenticationManager.authenticate(token);
            SecurityContext securityContext = SecurityContextHolder.getContext();
            securityContext.setAuthentication(auth);
    
            if(auth.isAuthenticated()){
                HttpSession session = request.getSession(true);
                session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
    
                LoginResponse loginResponse = new LoginResponse();
                loginResponse.setResponseCode(ResponseCodeType.SUCCESS);
                response = loginResponse;   
            }else{
                SecurityContextHolder.getContext().setAuthentication(null);
    
                ErrorResponse errorResponse = new ErrorResponse();
                errorResponse.setResponseCode(ResponseCodeType.ERROR);
                response = errorResponse;
            }   
        } catch (Exception e) {     
            ErrorResponse errorResponse = new ErrorResponse();
            errorResponse.setResponseCode(ResponseCodeType.ERROR);
            response = errorResponse;           
        }
        return response;
    }
    
    0 讨论(0)
  • 2020-11-28 05:40

    You can write your own security filter that will parse your JSON.

    http://docs.spring.io/spring-security/site/docs/3.0.x/reference/core-web-filters.html

    You can use the BasicAuthenticationFilter as a reference:

    http://docs.spring.io/spring-security/site/docs/3.0.x/apidocs/org/springframework/security/web/authentication/www/BasicAuthenticationFilter.html

    0 讨论(0)
  • 2020-11-28 05:42

    Here is the java configuration for the above solutions:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .addFilterBefore(authenticationFilter(),UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll();
    }
    
    @Bean
    public AuthenticationFilter authenticationFilter() throws Exception{
        AuthenticationFilter authenticationFilter = new AuthenticationFilter();
        authenticationFilter.setUsernameParameter("username");
        authenticationFilter.setPasswordParameter("password");
        authenticationFilter.setAuthenticationManager(authenticationManager());
        authenticationFilter.setFilterProcessesUrl("/login");
        authenticationFilter.setAuthenticationSuccessHandler(successHandler());
        return authenticationFilter;
    }
    
    @Bean
    public SuccessHandler successHandler(){
        return new SuccessHandler();
    }
    
    0 讨论(0)
  • 2020-11-28 05:44

    Look at this example: https://github.com/fuhaiwei/springboot_security_restful_api

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Autowired
        private CustomLoginHandler customLoginHandler;
    
        @Autowired
        private CustomLogoutHandler customLogoutHandler;
    
        @Autowired
        private CustomAccessDeniedHandler customAccessDeniedHandler;
    
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService);
        }
    
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/api/admin/**").hasRole("ADMIN")
                    .antMatchers("/api/basic/**").hasRole("BASIC")
                    .antMatchers("/api/session").permitAll()
                    .antMatchers(HttpMethod.GET).permitAll()
                    .antMatchers("/api/**").hasRole("BASIC");
    
            http.formLogin();
    
            http.logout()
                    .logoutUrl("/api/session/logout")
                    .addLogoutHandler(customLogoutHandler)
                    .logoutSuccessHandler(customLogoutHandler);
    
            http.exceptionHandling()
                    .accessDeniedHandler(customAccessDeniedHandler)
                    .authenticationEntryPoint(customAccessDeniedHandler);
    
            http.csrf()
                    .ignoringAntMatchers("/api/session/**");
    
            http.addFilterBefore(new AcceptHeaderLocaleFilter(), UsernamePasswordAuthenticationFilter.class);
    
            http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    
            http.addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(), CsrfFilter.class);
        }
    
        private CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
            CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
            filter.setAuthenticationSuccessHandler(customLoginHandler);
            filter.setAuthenticationFailureHandler(customLoginHandler);
            filter.setAuthenticationManager(authenticationManager());
            filter.setFilterProcessesUrl("/api/session/login");
            return filter;
        }
    
        private static void responseText(HttpServletResponse response, String content) throws IOException {
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
            response.setContentLength(bytes.length);
            response.getOutputStream().write(bytes);
            response.flushBuffer();
        }
    
        @Component
        public static class CustomAccessDeniedHandler extends BaseController implements AuthenticationEntryPoint, AccessDeniedHandler {
            // NoLogged Access Denied
            @Override
            public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
                responseText(response, errorMessage(authException.getMessage()));
            }
    
            // Logged Access Denied
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
                responseText(response, errorMessage(accessDeniedException.getMessage()));
            }
        }
    
        @Component
        public static class CustomLoginHandler extends BaseController implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
            // Login Success
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
                LOGGER.info("User login successfully, name={}", authentication.getName());
                responseText(response, objectResult(SessionController.getJSON(authentication)));
            }
    
            // Login Failure
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
                responseText(response, errorMessage(exception.getMessage()));
            }
        }
    
        @Component
        public static class CustomLogoutHandler extends BaseController implements LogoutHandler, LogoutSuccessHandler {
            // Before Logout
            @Override
            public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
    
            }
    
            // After Logout
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
                responseText(response, objectResult(SessionController.getJSON(null)));
            }
        }
    
        private static class AcceptHeaderLocaleFilter implements Filter {
            private AcceptHeaderLocaleResolver localeResolver;
    
            private AcceptHeaderLocaleFilter() {
                localeResolver = new AcceptHeaderLocaleResolver();
                localeResolver.setDefaultLocale(Locale.US);
            }
    
            @Override
            public void init(FilterConfig filterConfig) {
            }
    
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                Locale locale = localeResolver.resolveLocale((HttpServletRequest) request);
                LocaleContextHolder.setLocale(locale);
    
                chain.doFilter(request, response);
            }
    
            @Override
            public void destroy() {
            }
        }    
    }
    
    
    
    public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    
        @Override
        public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
            UsernamePasswordAuthenticationToken authRequest;
            try (InputStream is = request.getInputStream()) {
                DocumentContext context = JsonPath.parse(is);
                String username = context.read("$.username", String.class);
                String password = context.read("$.password", String.class);
                authRequest = new UsernamePasswordAuthenticationToken(username, password);
            } catch (IOException e) {
                e.printStackTrace();
                authRequest = new UsernamePasswordAuthenticationToken("", "");
            }
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题