I use spring security login. Now I'm trying to add spring social facebook login, but I get many error information.
First, when I try to use the same method like spring social guide, I can't #Autowired private Facebook facebook
I found a solution
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository
.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
Next, I get the error "cannot find bean". I have to add:
#Bean
public ConnectionRepository connectionRepository() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null) {
throw new IllegalStateException(
"Unable to get a ConnectionRepository: no user signed in");
}
return usersConnectionRepository().createConnectionRepository(
authentication.getName());
}
#Bean
public ConnectionFactoryLocator connectionFactoryLocator() {
ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(facebookid,
facebookSecure));
return registry;
}
#Bean
public AuthenticationNameUserIdSource authenticationNameUserIdSource(){
return new AuthenticationNameUserIdSource();
}
#Bean
public ConnectController connectController(
ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
return new ConnectController(connectionFactoryLocator,
connectionRepository);
}
#Bean
public UsersConnectionRepository usersConnectionRepository() {
return new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator(), Encryptors.noOpText());
}
After that, I have other issue java.lang.NoSuchMethodError: org.springframework.social.security.SocialAuthenticationFilter.getFilterProcessesUrl()Ljava/lang/String;
#Bean
public SocialAuthenticationServiceLocator socialAuthenticationServiceLocator() {
SocialAuthenticationServiceRegistry registry = new SocialAuthenticationServiceRegistry();
registry.addConnectionFactory(new FacebookConnectionFactory(facebookid,
facebookSecure));
return registry;
}
#Bean
public SocialAuthenticationFilter socialAuthenticationFilter()
throws Exception {
SocialAuthenticationFilter filter = new SocialAuthenticationFilter(
authenticationManager(), authenticationNameUserIdSource(),
usersConnectionRepository(), socialAuthenticationServiceLocator());
filter.setFilterProcessesUrl("/login");
filter.setSignupUrl("/signup");
filter.setConnectionAddedRedirectUrl("/home");
filter.setPostLoginUrl("/home"); // always open account profile
// page after login
// filter.setRememberMeServices(rememberMeServices());
return filter;
}
but always is the same.
This is my http configuration
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/home", "/css/**", "/**/*.css*", "/", "/signup",
"/facebook", "/signup.xhtml").permitAll().anyRequest()
.authenticated().and().formLogin().loginPage("/login").loginProcessingUrl("/login/authenticate")
.defaultSuccessUrl("/home").failureUrl("/login")
.permitAll().and().logout().logoutUrl("/logout")
.invalidateHttpSession(true).logoutSuccessUrl("/").and()
.apply(new SpringSocialConfigurer());
And controller
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String loginPage() {
return "redirect:/login/authenticate/connect/facebook";
}
I did a whole tutorial. Next, I removed SocialConfigurer implementation and created the same (not #Override, only #Bean) social documentation.
'Normal login '(spring security) works fine, but I can't configure spring social with spring security. I use JSF and .XHTML files.
Maybe someone knows where I make the mistakes?
Thanks for your help.
It looks like Spring Security removed getFilterProcessesUrl() in Spring Security 4.0.0.RC1 (it was marked as deprecated anyways).
It seems that other project filters have not been updated?
Try rolling back to 4.0.0.M2 or use the 3.2 train.
Please notice that spring security 4 will not accept spring social 1.1.0. Please upgrade all spring social dependencies(config, core, security and web) to 1.1.2.RELEASE. You can leave your spring social Facebook to 1.1.0
As hinted in my comment, you have the wrong version of some library. My intelligent guess is that version of Spring Security is wrong. From what I can find, you should use a version in the 3.2.x series (for example 3.2.5) of Spring Security.
Consider using version 1.1.4.
this is solved in spring-social-security 1.1.4.RELEASE (or perhaps some version before).
https://github.com/spring-projects/spring-social
Related
Looking at the new spring-security-oauth2-authorization-server library, I need to implement a custom authorization server for a resource server. Actually, I intend to manually implement the PRIVATE_KEY_JWT Authentication method that is either not yet implemented or lacking examples. I followed one good example that uses this approach (the authorization server issuing the access token to the resource server). The resource server has a simple configuration that points to my issuer (authorization server) as in the code:
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://auth-server:9000
but this approach uses client-id and client-secret credentials like:
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("client_id123")
.clientSecret("{noop}secret123")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("https://purl.imsglobal.org/spec/lti-ags/scope/score")
.build();
return new InMemoryRegisteredClientRepository(registeredClient);
}
I appreciate if anyone could refer me an example of either a PRIVATE_KEY_JWT implementation or a custom authorization implementation that receives a JWT and issues an access token.
In application.yml set up the location of the certs, and create a config class RsaKeyProperties to load them:
rsa:
privateKey: classpath:certs/private.pem
publicKey: classpath:certs/public.pem
Then in WebSecurityConfig:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
protected SecurityFilterChain configure(
BasicAuthFilter basicAuthFilter,
ActivitiAuthenticationFilter activitiAuthenticationFilter,
HttpSecurity http) throws Exception {
return http
// Disabling CSRF is safe for token-based API's
.csrf().disable()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(auth -> {
auth.antMatchers(
"/api/authenticate/**",
"/api/tenants/**").permitAll();
auth.antMatchers("/api/**").authenticated();
// When an exception is thrown, ErrorMvcAutoConfiguration sets stuff up so that /error is called
// internally using an anonymous user. Without this line, the call to /error fails with a 403 error
// because anonymous users would not be able to view the page.
auth.antMatchers("/error").anonymous();
})
.build();
}
#Bean
public JwtDecoder jwtDecoder(RsaKeyProperties rsaKeyProperties) {
return NimbusJwtDecoder.withPublicKey(rsaKeyProperties.publicKey()).build();
}
#Bean
public JwtEncoder jwtEncoder(RsaKeyProperties rsaKeyProperties) {
JWK jwk = new RSAKey.Builder(rsaKeyProperties.publicKey())
.privateKey(rsaKeyProperties.privateKey())
.build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
}
The "SCOPE_" prefix that the identity server library was adding in the JWT didn't work well with spring security as far as I could figure out, so I removed that:
/**
* For some reason the JwtGrantedAuthoritiesConverter defaults to adding the prefix "SCOPE_" to all
* the claims in the token, so we need to provide a JwtGrantedAuthoritiesConverter that doesn't do
* that and just passes them through.
*/
#Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
grantedAuthoritiesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter authConverter = new JwtAuthenticationConverter();
authConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return authConverter;
}
I am currently developing an application with SpringBoot 2.0.0.M7 and spring-boot-starter-webflux. It also use Spring Boot WebFlux Functional Rest API and Spring Boot Actuator.
Application itself is very simple and it works as expected. I can POST book (/api/v1/book) to the ArrayList and GET all books (/api/v1/book) from ArrayList. Rest API Documentation is available on (/) URL and Actuator endpoint is available on (/actuator) URL.
Problem is that how can I secure my WebFlux Functional Rest API with OAuth2 and JWT? I can find many examples OAuth2 with JWT but they are for older Spring Boot. Here is one example https://dzone.com/articles/spring-oauth2-with-jwt-sample SpringBoot 2.0.0.M7 has a support on OAuth2 Login but this example https://spring.io/blog/2017/09/15/spring-security-5-0-0-m4-released does not tell how to use OAuth2 with JWT in a Rest API. I did not even find a full source code of that example. This video https://www.youtube.com/watch?v=LzNhedHo7pM and this example https://medium.com/#mohitsinha.it/spring-boot-reactive-tutorial-ffab309b2dcd will only use a basic authentication.
Does anyone know how I can secure my (/api/v1/book) endpoint using #EnableWebFluxSecurity annotation and OAuth2 with JWT?
Below has example on my RoutingConfiguration class.
#Configuration
#EnableWebFlux
public class RoutingConfiguration implements WebFluxConfigurer {
private final static String API_URL = "api";
private final MeterRegistry registry;
public RoutingConfiguration(MeterRegistry registry) {
this.registry = registry;
}
#Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping(API_URL + "/**").allowedMethods("GET", "POST");
}
#Bean
public RouterFunction<?> indexRouter(final IndexHandler indexHandler, final ErrorHandler errorHandler) {
RouterFunctionMetrics metrics = new RouterFunctionMetrics(this.registry);
return route(GET("/"), indexHandler::getRestApiDocs).filter(metrics.timer("http.request.index.page"));
}
#Bean
public RouterFunction<?> bookRouter(final BookHandler handler, final ErrorHandler errorHandler) {
return
nest(path(API_URL + "/v1/book"),
nest(accept(MediaType.APPLICATION_JSON),
route(GET("/"), handler::getAllBooks)
.andRoute(POST("/"), handler::saveBook)
).andOther(route(RequestPredicates.all(), errorHandler::notFound))
);
}
}
I am composing spring-social. When I click the / signin / facebook button:
Exception while completing OAuth 2 connection:
Org.springframework.social.MissingAuthorizationException:
Authorization is required for the operation, but the API was created
without authorization.
I have encountered this error.
As a result of debugging, facebook access token seems to be well received.
An error occurs in this part after normal login of Facebook.
ConnectSupport class:
AccessGrant accessGrant = connectionFactory.getOAuthOperations().exchangeForAccess(code, callbackUrl(request), null);
This is my composition.
SocialConfig:
#Inject
private DataSource dataSource;
#Autowired
UserRepository userRepository;
#Autowired
PostSocialSignInAdapter postSocialSignInAdapter;
#Override
public UserIdSource getUserIdSource() {
return new CUserIdSource();
}
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
String appKey = "4123213121231231212331";
String appSecret = "512312312lkjj1232kj3l1jl213";
cfConfig.addConnectionFactory(new FacebookConnectionFactory(appKey, appSecret));
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,
connectionFactoryLocator, Encryptors.noOpText());
repository.setConnectionSignUp(new SocialImplicitSignUp(userRepository));
return repository;
}
#Bean
#Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
#Bean
public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator,
UsersConnectionRepository usersConnectionRepository) {
ProviderSignInController providerSignInController = new ProviderSignInController(connectionFactoryLocator,
usersConnectionRepository, postSocialSignInAdapter);
providerSignInController.setSignUpUrl("/");
return providerSignInController;
}
UserRepository interface
public interface UserRepository extends JpaRepository<User, String> {
public User findByusername(String username);
}
error log
ERROR: org.springframework.social.connect.web.ProviderSignInController - Exception while completing OAuth 2 connection:
org.springframework.social.MissingAuthorizationException: Authorization is required for the operation, but the API binding was created without authorization.
at org.springframework.social.facebook.api.impl.AbstractFacebookOperations.requireAuthorization(AbstractFacebookOperations.java:30)
at org.springframework.social.facebook.api.impl.UserTemplate.getUserProfile(UserTemplate.java:48)
at org.springframework.social.facebook.connect.FacebookAdapter.setConnectionValues(FacebookAdapter.java:42)
at org.springframework.social.facebook.connect.FacebookAdapter.setConnectionValues(FacebookAdapter.java:30)
at org.springframework.social.connect.support.AbstractConnection.setValues(AbstractConnection.java:175)
at org.springframework.social.connect.support.AbstractConnection.initKey(AbstractConnection.java:137)
at org.springframework.social.connect.support.OAuth2Connection.<init>(OAuth2Connection.java:75)
I do not know how to do this. Please help me.
This can be related to many causes but at first you should check your spring-social-facebook version. Since Facebook graph api 2.2 deprecation, Spring recommendation is to change the version to 2.0.3.RELEASE.
Someone else was talking about a similar issue here "similar post
Don't try the 2.0.2.RELEASE, AFAIK there is an issue with inconsistent property type unmapped on json call.
I am currently building a single page AngularJS application which communicates via REST to a backend. The structure is as follow:
One Spring MVC WebApp project which contains all AngularJS pages and resources and all REST controllers.
A true backend which has services and repositories for backend communication, an API if you will. The REST calls will talk to these service (the second project is included as a dependency of the first one).
I have been thinking about this a lot but I can't seem to find anything that can help me. Basically I just need some security on this application. I'd like some kind of session management which is extremely simple:
user logs in, session id is created and stored in JS/cookie on
website
when user would reload page/ come back later a check needs to be done to see if the session id is still valid
no calls should reach the controllers if the session id is not valid
This is the general idea of basic session managament, what would be the easiest way to get this implemented in a Spring MVC webapp (no JSP's, just angular and REST controllers).
Thanks in advance!
You have 2 options for the rest API: stateful or stateless.
1st option: HTTP session authentication - the "classical" Spring Security authentication mechanism. If you plan to scale your application on multiple servers, you need to have a load balancer with sticky sessions so that each user stays on the same server (or use Spring Session with Redis).
2nd option: you have the choice of OAuth or token-based authentication.
OAuth2 is a stateless security mechanism, so you might prefer it if you want to scale your application across several machines. Spring Security provides an OAuth2 implementation. The biggest issue with OAuth2 is that requires to have several database tables in order to store its security tokens.
Token-based authentication, like OAuth2, is a stateless security mechanism, so it's another good option if you want to scale on several different servers. This authentication mechanism doesn't exist by default with Spring Security. It is easier to use and implement than OAuth2, as it does not require a persistence mechanism, so it works on all SQL and NoSQL options. This solution uses a custom token, which is a MD5 hash of your user name, the expiration date of the token, your password, and a secret key. This ensures that if someone steals your token, he should not be able to extract your username and password.
I recommend you to look into JHipster. It will generate a web app skeleton for you with REST API using Spring Boot and the front end using AngularJS. When generating the application skeleton it will ask you to choose between the 3 authentication mechanisms that I described above. You can reuse the code that JHipster will generate in your Spring MVC application.
Here is an example of TokenProvider generated by JHipster:
public class TokenProvider {
private final String secretKey;
private final int tokenValidity;
public TokenProvider(String secretKey, int tokenValidity) {
this.secretKey = secretKey;
this.tokenValidity = tokenValidity;
}
public Token createToken(UserDetails userDetails) {
long expires = System.currentTimeMillis() + 1000L * tokenValidity;
String token = userDetails.getUsername() + ":" + expires + ":" + computeSignature(userDetails, expires);
return new Token(token, expires);
}
public String computeSignature(UserDetails userDetails, long expires) {
StringBuilder signatureBuilder = new StringBuilder();
signatureBuilder.append(userDetails.getUsername()).append(":");
signatureBuilder.append(expires).append(":");
signatureBuilder.append(userDetails.getPassword()).append(":");
signatureBuilder.append(secretKey);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No MD5 algorithm available!");
}
return new String(Hex.encode(digest.digest(signatureBuilder.toString().getBytes())));
}
public String getUserNameFromToken(String authToken) {
if (null == authToken) {
return null;
}
String[] parts = authToken.split(":");
return parts[0];
}
public boolean validateToken(String authToken, UserDetails userDetails) {
String[] parts = authToken.split(":");
long expires = Long.parseLong(parts[1]);
String signature = parts[2];
String signatureToMatch = computeSignature(userDetails, expires);
return expires >= System.currentTimeMillis() && signature.equals(signatureToMatch);
}
}
SecurityConfiguration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private UserDetailsService userDetailsService;
#Inject
private TokenProvider tokenProvider;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Inject
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/scripts/**/*.{js,html}");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/protected/**").authenticated()
.and()
.apply(securityConfigurerAdapter());
}
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
private static class GlobalSecurityConfiguration extends GlobalMethodSecurityConfiguration {
}
private XAuthTokenConfigurer securityConfigurerAdapter() {
return new XAuthTokenConfigurer(userDetailsService, tokenProvider);
}
/**
* This allows SpEL support in Spring Data JPA #Query definitions.
*
* See https://spring.io/blog/2014/07/15/spel-support-in-spring-data-jpa-query-definitions
*/
#Bean
EvaluationContextExtension securityExtension() {
return new EvaluationContextExtensionSupport() {
#Override
public String getExtensionId() {
return "security";
}
#Override
public SecurityExpressionRoot getRootObject() {
return new SecurityExpressionRoot(SecurityContextHolder.getContext().getAuthentication()) {};
}
};
}
}
And the respective AngularJS configuration:
'use strict';
angular.module('jhipsterApp')
.factory('AuthServerProvider', function loginService($http, localStorageService, Base64) {
return {
login: function(credentials) {
var data = "username=" + credentials.username + "&password="
+ credentials.password;
return $http.post('api/authenticate', data, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json"
}
}).success(function (response) {
localStorageService.set('token', response);
return response;
});
},
logout: function() {
//Stateless API : No server logout
localStorageService.clearAll();
},
getToken: function () {
return localStorageService.get('token');
},
hasValidToken: function () {
var token = this.getToken();
return token && token.expires && token.expires > new Date().getTime();
}
};
});
authInterceptor:
.factory('authInterceptor', function ($rootScope, $q, $location, localStorageService) {
return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
var token = localStorageService.get('token');
if (token && token.expires && token.expires > new Date().getTime()) {
config.headers['x-auth-token'] = token.token;
}
return config;
}
};
})
Add authInterceptor to $httpProvider:
.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
})
Hope this is helpful!
This video from SpringDeveloper channel may be useful too: Great single page apps need great backends. It talks about some best practices (including session management) and demos working code examples.
Take a look from what has been done in JHipster https://jhipster.github.io/. You can even use it.
Jhipster is a spring boot + angular/angularjs generator. I often use it to inspire me and learn best practice.
I am using Spring Security with OAuth2. It's working fine except login success and failure handlers.
Like in spring web security OAuth2 does not have clearly defined success and failure handlers hooks to update DB and set response accordingly.
What filter do I need to extend and what should its position be in the Spring Security filter chain?
Specify successHandler and failureHandler for oauth2login method:
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${successUrl}")
private String successUrl;
#Value("${failureUrl}")
private String failureUrl;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.oauth2Login()
.successHandler(successHandler())
.failureHandler(failureHandler());
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
return new SimpleUrlAuthenticationSuccessHandler(successUrl);
}
#Bean
SimpleUrlAuthenticationFailureHandler failureHandler() {
return new SimpleUrlAuthenticationFailureHandler(failureUrl);
}
}
Tested for Spring Security 5.0.6
I personally use
#Component
public class MyAuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
#Override
public void onApplicationEvent(AuthenticationSuccessEvent event) {
System.out.println("Authenticated");
}
}
Additional informations in response can be set by CustomTokenEnhancer
This is a nice tutorial about how to use spring boot with oauth2. Down to the road they show how to configure sso filter by hand:
private Filter ssoFilter(OAuth2Configuration client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
filter.setTokenServices(new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId()));
//THIS IS THE PLACE YOU CAN SET THE HANDLER
filter.setAuthenticationSuccessHandler(savedRequestAwareAuthenticationSuccessHandler());
return filter;
}
They didn't provide the line you need, here it is.
The success handler and failure handler are defined in the form-login (if you use Spring's XML). It is not different than any other spring-security definitions:
<security:form-login
login-page="/login/login.htm"
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-url="/login/login.htm?login_error=1" />
and you can find the handler here.
The "failure handler" is pretty similar.