首页 文章

Spring Boot Spring Security Spring OAuth2 Google登录

提问于
浏览
13

我已经设置了一个小项目,使用Spring Boot(1.5.2),Spring Security和Spring Security OAuth2实现OAuth2使用Google API登录 .

您可以在以下位置找到来源:https://github.com/ccoloradoc/OAuth2Sample

我可以通过谷歌进行身份验证并提取用户信息 . 但是,在我注销后,由于我得到"400 Bad Request"后,我无法再次登录,之后我尝试将“https://accounts.google.com/o/oauth2/auth”与我的RestTemplate连接以调用google api .

有关进一步参考,请参阅Filter attemptAuthentication方法 .

这是我的安全配置类

@Configuration
@EnableGlobalAuthentication
@EnableOAuth2Client
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@PropertySource(value = {"classpath:oauth.properties"})
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


    @Autowired
    private UserDetailsService userDetailsService;

    @Resource
    @Qualifier("accessTokenRequest")
    private AccessTokenRequest accessTokenRequest;

    @Autowired
    private OAuth2ClientContextFilter oAuth2ClientContextFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.
                authorizeRequests()
                .antMatchers(HttpMethod.GET, "/login","/public/**", "/resources/**","/resources/public/**").permitAll()
                .antMatchers("/google_oauth2_login").anonymous()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/login")
                .defaultSuccessUrl("/")
                .and()
                .csrf().disable()
                .logout()
                .logoutSuccessUrl("/")
                .logoutUrl("/logout")
                .deleteCookies("remember-me")
                .and()
                .rememberMe()
                .and()
                .addFilterAfter(oAuth2ClientContextFilter,ExceptionTranslationFilter.class)
                .addFilterAfter(googleOAuth2Filter(),OAuth2ClientContextFilter.class)
                .userDetailsService(userDetailsService);
        // @formatter:on
    }

    @Bean
    @ConfigurationProperties("google.client")
    public OAuth2ProtectedResourceDetails auth2ProtectedResourceDetails() {
        return new AuthorizationCodeResourceDetails();
    }

    @Bean
    public OAuth2RestTemplate oauth2RestTemplate() {
        return new OAuth2RestTemplate(auth2ProtectedResourceDetails(),
                new DefaultOAuth2ClientContext(accessTokenRequest));
    }


    @Bean
    public GoogleOAuth2Filter googleOAuth2Filter() {
        return new GoogleOAuth2Filter("/google_oauth2_login");
    }

    /*
    *  Building our custom Google Provider
    * */
    @Bean
    public GoogleOauth2AuthProvider googleOauth2AuthProvider() {
        return new GoogleOauth2AuthProvider();
    }

    /*
    *  Using autowired to assign it to the auth manager
    * */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(googleOauth2AuthProvider());
    }

    @Bean
    public SpringSecurityDialect springSecurityDialect() {
        return new SpringSecurityDialect();
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

}

这是我的身份验证提供者:

public class GoogleOauth2AuthProvider implements AuthenticationProvider {

    private static final Logger logger = LoggerFactory.getLogger(GoogleOauth2AuthProvider.class);

    @Autowired(required = true)
    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        logger.info("Provider Manager Executed");
        CustomOAuth2AuthenticationToken token = (CustomOAuth2AuthenticationToken) authentication;
        UserDetailsImpl registeredUser = (UserDetailsImpl) token.getPrincipal();
        try {
            registeredUser = (UserDetailsImpl) userDetailsService
                    .loadUserByUsername(registeredUser.getEmail());
        } catch (UsernameNotFoundException usernameNotFoundException) {
            logger.info("User trying google/login not already a registered user. Register Him !!");
        }
        return token;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return CustomOAuth2AuthenticationToken.class
                .isAssignableFrom(authentication);
    }
}

UserDetailService是Spring安全核心的一个实现,它从数据库读取用户并将其转换为实现Spring安全核心UserDetails的UserDetails POJO .

这是我的过滤器实现:

public class GoogleOAuth2Filter extends AbstractAuthenticationProcessingFilter {

    /**
     * Logger
     */
    private static final Logger log = LoggerFactory.getLogger(GoogleOAuth2Filter.class);

    private static final Authentication dummyAuthentication;

    static {
        dummyAuthentication = new UsernamePasswordAuthenticationToken(
                "dummyUserName23452346789", "dummyPassword54245",
                CustomUserDetails.DEFAULT_ROLES);
    }

    private static final String NAME = "name";
    private static final String EMAIL = "email";
    private static final String PICTURE = "picture";

    private static final Logger logger = LoggerFactory
            .getLogger(GoogleOAuth2Filter.class);


    @Value(value = "${google.authorization.url}")
    private String googleAuhorizationUrl;

    public GoogleOAuth2Filter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }

    @Autowired
    private UserService userService;

    @Autowired
    private OAuth2RestTemplate oauth2RestTemplate;

    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException,
            IOException, ServletException {
        logger.info("Google Oauth Filter Triggered!!");
        URI authURI;
        try {
            authURI = new URI(googleAuhorizationUrl);
        } catch (URISyntaxException e) {
            log.error("\n\n\n\nERROR WHILE CREATING GOOGLE AUTH URL", e);
            return null;
        }
        SecurityContext context = SecurityContextHolder.getContext();
        // auth null or not authenticated.
        String code = request.getParameter("code");
        Map<String, String[]> parameterMap = request.getParameterMap();
        logger.debug(parameterMap.toString());
        if (StringUtils.isEmpty(code)) {
            // Google authentication in progress. will return null.
            logger.debug("Will set dummy user in context ");
            context.setAuthentication(dummyAuthentication);
            // trigger google oauth2.
            // ERROR ON SECOND LOGIN ATTEMPT
            oauth2RestTemplate.postForEntity(authURI, null, Object.class);
            return null;
        } else {
            logger.debug("Response from Google Recieved !!");

            ResponseEntity<Object> forEntity = oauth2RestTemplate.getForEntity(
                    "https://www.googleapis.com/plus/v1/people/me/openIdConnect",
                    Object.class);

            @SuppressWarnings("unchecked")
            Map<String, String> profile = (Map<String, String>) forEntity.getBody();

            CustomOAuth2AuthenticationToken authenticationToken = getOAuth2Token(
                    profile.get(EMAIL), profile.get(NAME), profile.get(PICTURE));
            authenticationToken.setAuthenticated(false);

            return getAuthenticationManager().authenticate(authenticationToken);
        }
    }

    private CustomOAuth2AuthenticationToken getOAuth2Token(
            String email, String name, String picture) {

        User user = userService.findByEmail(email);
        //Register user
        if(user == null) {
            user = new User(name, email, picture);
            userService.saveOrUpdate(user);
        }

        UserDetailsImpl registeredUser = new UserDetailsImpl(name, email, picture);

        CustomOAuth2AuthenticationToken authenticationToken =
                new CustomOAuth2AuthenticationToken(registeredUser);

        return authenticationToken;
    }

}

2 回答

  • 4

    如果你使用 EnableOAuth2Sso 方法(虽然它隐藏了很多你的过程),事情变得容易多了 . The Spring Boot tutorial on OAuth2对此非常彻底,我在网上有其他一些例子(例如https://github.com/SoatGroup/spring-boot-google-auth/http://dreamix.eu/blog/java/configuring-google-as-oauth2-authorization-provider-in-spring-boot),这些例子有所帮助 . 最终,this was the resource帮助了我最多 - 涵盖整个流程和集成客户端应用程序 .

    如果你想在较低级别执行此操作,那么有很多关于整个过程以及它如何在Spring中工作的详细信息Pivotal blog post .

  • 3

    谢谢Cristian,你不知道你的代码有多少为我自己的代码奠定了基础 . 我修改了您的原始OAuth2 Github项目并将其更改为以下代码 .

    GoogleOAuth2Filter.java

    package tech.aabo.celulascontentas.oauth.filter;
    
    import static java.lang.Math.toIntExact;
    import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl;
    import com.google.api.client.auth.oauth2.TokenResponse;
    import com.google.api.client.auth.oauth2.TokenResponseException;
    import com.google.api.client.googleapis.auth.oauth2.*;
    import com.google.api.client.http.HttpTransport;
    import com.google.api.client.http.javanet.NetHttpTransport;
    import com.google.api.client.json.JsonFactory;
    import com.google.api.client.json.jackson2.JacksonFactory;
    import com.google.api.services.plus.Plus;
    import com.google.api.services.plus.model.Person;
    import org.apache.commons.lang3.StringUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.configurationprocessor.json.JSONException;
    import org.springframework.boot.configurationprocessor.json.JSONObject;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.context.SecurityContext;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.oauth2.client.OAuth2RestTemplate;
    import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import tech.aabo.celulascontentas.oauth.domain.User;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.math.BigInteger;
    import java.sql.Timestamp;
    import java.time.Instant;
    import java.util.Arrays;
    import java.util.Calendar;
    import java.util.UUID;
    
    /**
     * Created by colorado on 9/03/17.
     * Modified by frhec on 7/06/18
     */
    public class GoogleOAuth2Filter extends AbstractAuthenticationProcessingFilter {
    /**
     * Logger
     */
    private static final Logger logger = LoggerFactory.getLogger(GoogleOAuth2Filter.class);
    
    
    public GoogleOAuth2Filter(String defaultFilterProcessesUrl) {
        super(defaultFilterProcessesUrl);
    }
    
    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }
    
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String CLIENT_SECRET_FILE = "client_secret.json";
    
        SecurityContext context = SecurityContextHolder.getContext();
    
        if(context.getAuthentication() == null) {
    
            GoogleClientSecrets clientSecrets = loadSecret(CLIENT_SECRET_FILE);
    
            if (StringUtils.isEmpty(request.getQueryString())) {
                try {
                    GoogleAuthorizationCodeRequestUrl auth = new GoogleAuthorizationCodeRequestUrl(clientSecrets.getDetails().getClientId(),
                            request.getRequestURL().toString(), Arrays.asList(
                            "https://www.googleapis.com/auth/plus.login",
                            "https://www.googleapis.com/auth/plus.me",
                            "https://www.googleapis.com/auth/plus.profile.emails.read")).setState("/user");
                    auth.setAccessType("offline");
                    response.addHeader("Place","Before");
                    response.sendRedirect(auth.build());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            } else {
    
                response.addHeader("Place","After");
                AuthorizationCodeResponseUrl authResponse = new AuthorizationCodeResponseUrl(transformName(request, 0));
                // check for user-denied error
                if (authResponse.getError() != null) {
                    logger.info("Denied");
                } else {
                    try {
                        assert clientSecrets != null;
                        Calendar calendar = Calendar.getInstance();
    
                        NetHttpTransport net = new NetHttpTransport();
                        JacksonFactory jackson = new JacksonFactory();
    
                        GoogleTokenResponse tokenResponse =
                                new GoogleAuthorizationCodeTokenRequest(net, jackson,
                                        clientSecrets.getDetails().getClientId(), clientSecrets.getDetails().getClientSecret(),
                                        authResponse.getCode(), transformName(request, 1))
                                        .execute();
    
                        // Use access token to call API
                        GoogleCredential credential;
    
                        if (tokenResponse.getRefreshToken() == null) {
                            credential = new GoogleCredential();
                            credential.setFromTokenResponse(tokenResponse);
                        } else {
                            credential = createCredentialWithRefreshToken(net, jackson, clientSecrets, tokenResponse);
                        }
    
                        Plus plus =
                                new Plus.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
                                        .setApplicationName("Google Plus Profile Info")
                                        .build();
    
                        Person profile = plus.people().get("me").execute();
    
                        // Get profile info from ID token
                        GoogleIdToken idToken = tokenResponse.parseIdToken();
                        GoogleIdToken.Payload payload = idToken.getPayload();
    
                        User auth = new User();
    
                        auth.setAccessToken(tokenResponse.getAccessToken());
                        auth.setId(new BigInteger(payload.getSubject().trim())); // Use this value as a key to identify a user.
                        auth.setUuid(UUID.randomUUID().toString());
                        auth.setEmail(payload.getEmail());
                        auth.setVerifiedEmail(payload.getEmailVerified());
                        auth.setName(profile.getDisplayName());
                        auth.setPictureURL(profile.getImage().getUrl());
                        auth.setLocale(profile.getLanguage());
                        auth.setFamilyName(profile.getName().getFamilyName());
                        auth.setGivenName(profile.getName().getGivenName());
                        auth.setStatus(true);
                        auth.setExpired(false);
                        auth.setLocked(false);
                        auth.setExpiredCredentials(false);
                        auth.setRoles("USER");
                        auth.setRefreshToken(tokenResponse.getRefreshToken());
                        auth.setDateCreated(calendar.getTime());
                        calendar.add(Calendar.SECOND, toIntExact(tokenResponse.getExpiresInSeconds()));
                        auth.setExpirationDate(calendar.getTime());
                        auth.setDateModified(Calendar.getInstance().getTime());
    
                        Authentication authenticationToken = getOAuth2Token(auth);
    
                        request.authenticate(response);
    
                        if (//Validation happening) {
                            authenticationToken.setAuthenticated(true);
                        } else {
                            authenticationToken.setAuthenticated(false);
                        }
    
                        return authenticationToken;
    
                    } catch (TokenResponseException e) {
                        if (e.getDetails() != null) {
                            System.err.println("Error: " + e.getDetails().getError());
                            if (e.getDetails().getErrorDescription() != null) {
                                System.err.println(e.getDetails().getErrorDescription());
                            }
                            if (e.getDetails().getErrorUri() != null) {
                                System.err.println(e.getDetails().getErrorUri());
                            }
                        } else {
                            System.err.println(e.getMessage());
                        }
                    } catch (IOException | ServletException e) {
                        e.printStackTrace();
                    }
                }
    
            }
        }else if(!context.getAuthentication().isAuthenticated()) {
            setResponseUnauthenticated(response);
        }else{
            try {
                response.sendRedirect(transformName(request,2)+"/user");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    
    private void setResponseUnauthenticated(HttpServletResponse response){
        try {
    
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            PrintWriter out = response.getWriter();
    
            //create Json Object
            JSONObject values = new JSONObject();
    
            values.put("principal", null);
    
            values.put("authentication", null);
            values.put("timestamp", String.valueOf(Timestamp.from(Instant.now())));
            values.put("code",401);
            values.put("message", "Not Authorized");
    
            out.print(values.toString());
        } catch (JSONException | IOException e) {
            e.printStackTrace();
        }
    }
    
    public static GoogleCredential createCredentialWithRefreshToken(HttpTransport transport,
                                                                    JsonFactory jsonFactory,
                                                                    GoogleClientSecrets clientSecrets,
                                                                    TokenResponse tokenResponse) {
        return new GoogleCredential.Builder().setTransport(transport)
                .setJsonFactory(jsonFactory)
                .setClientSecrets(clientSecrets)
                .build()
                .setFromTokenResponse(tokenResponse);
    }
    
    
    public static String transformName(HttpServletRequest request, Integer type){
    
        switch(type) {
            case 0:
                return request.getScheme() + "://" +   // "http" + "://
                        request.getServerName() +       // "myhost"
                        ":" +                           // ":"
                        request.getServerPort() +       // "8080"
                        request.getRequestURI() +       // "/people"
                        "?" +                           // "?"
                        request.getQueryString();       // "lastname=Fox&age=30"
            case 1:
                return request.getScheme() + "://" +   // "http" + "://
                        request.getServerName() +       // "myhost"
                        ":" +                           // ":"
                        request.getServerPort() +       // "8080"
                        request.getRequestURI();      // "/people"
            case 2:
                return request.getScheme() + "://" +   // "http" + "://
                        request.getServerName() +       // "myhost"
                        ":" +                           // ":"
                        request.getServerPort();        // "8080"
            default:
                return request.getScheme() + "://" +   // "http" + "://
                        request.getServerName() +       // "myhost"
                        ":" +                           // ":"
                        request.getServerPort() +       // "8080"
                        request.getRequestURI() +       // "/people"
                        "?" +                           // "?"
                        request.getQueryString();       // "lastname=Fox&age=30"
        }
    }
    
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    
        SecurityContextHolder.getContext().setAuthentication(authResult);
    
        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }
    
        response.sendRedirect(transformName(request,2)+"/user");
    
    
    }
    
    private CustomOAuth2AuthenticationToken getOAuth2Token(User auth) {
    
        return new CustomOAuth2AuthenticationToken(auth);
    }
    
    private GoogleClientSecrets loadSecret(String name){
        ClassPathResource resource = new ClassPathResource(name);
        try {
            // Exchange auth code for access token
            return GoogleClientSecrets.load(JacksonFactory.getDefaultInstance(), new FileReader(resource.getFile()));
        } catch (IOException e) {
            return null;
        }
    }
    
    }
    

    我还将主要的Security类更改为:

    private GoogleOAuth2Filter googleOAuth2Filter = new GoogleOAuth2Filter("/login/google");
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
         // @formatter:off
         http.antMatcher("/**")
                .authorizeRequests()
                   .antMatchers("/", "/login/google", "/error**").permitAll().anyRequest().authenticated()
                 .and().exceptionHandling().authenticationEntryPoint((request, response, e) -> {
                     //create Json Object
                     try {
                          JSONObject values = new JSONObject();
                          values.put("principal", JSONObject.NULL);
                          values.put("authentication", JSONObject.NULL);
                          values.put("timestamp", String.valueOf(Timestamp.from(Instant.now())));
                          values.put("code",401);
                          values.put("message", "Not Authorized");
    
                          response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                          response.setContentType("application/json");
                          response.setCharacterEncoding("UTF-8");
                          response.getWriter().write(values.toString());
                      } catch (JSONException | IOException f) {
                         f.printStackTrace();
                      }
                    })
                .and().addFilterBefore(googleOAuth2Filter, BasicAuthenticationFilter.class);
            // @formatter:on
    }
    

    我还为/ user和/ logout创建了自定义映射 .

    希望它可以在将来帮助某人

相关问题