I've implemented Spring Social and have successfully been able to implement the ProviderSignInController to authenticate connections to Facebook.
As part of this I needed to implement the SignInAdapter interface. I understand this interface is used in the final steps of a successful authentication with a provider, specifically I'm overriding the signIn method to log the client into my application programmatically after successful authentication with the provider.
My problem is that when I implement the SignInAdapter it doesn't fire the signIn() method on successful login.
The code is almost straight out of the Spring showcase examples:
public class SimpleSignInAdapter implements SignInAdapter {
private static final Logger logger = LoggerFactory.getLogger(SimpleSignInAdapter.class);
private final RequestCache requestCache;
#Inject
public SimpleSignInAdapter(RequestCache requestCache) {
this.requestCache = requestCache;
logger.debug("Constructing " + SimpleSignInAdapter.class.getCanonicalName());
}
#Override
public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
/* A social profile has been found. Now we need to log that user into the
* application programatically.
*
* No other credentials are necessary here because by the time this method
* is called the user will have signed into the provider and their connection
* with that provider has been used to prove the user's identity.
*/
logger.debug("A social profile has been found. Now we need to log that user into the app.");
SignInUtils.signin(localUserId);
return null;
}
private String extractOriginalUrl(NativeWebRequest request) {
HttpServletRequest nativeReq = request.getNativeRequest(HttpServletRequest.class);
HttpServletResponse nativeRes = request.getNativeResponse(HttpServletResponse.class);
SavedRequest saved = requestCache.getRequest(nativeReq, nativeRes);
if (saved == null) {
return null;
}
requestCache.removeRequest(nativeReq, nativeRes);
removeAutheticationAttributes(nativeReq.getSession(false));
return saved.getRedirectUrl();
}
private void removeAutheticationAttributes(HttpSession session) {
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
The interesting this is that the SimpleSignInAdapter is constructed as I can debug through that step and see my debug output in the log so it seems the Bean is being instantiated just not firing the signIn method.
Here's my Spring configuration:
#Configuration
#ComponentScan(basePackages = "com.mycompany.webclient")
#PropertySource("classpath:app.properties")
#ImportResource("/WEB-INF/spring/appServlet/security-app-context.xml")
public class MainConfig {
#Bean
public PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
And my Spring Social config is:
#Configuration
#EnableSocial
public class SocialConfig implements SocialConfigurer {
private SocialUserDAO socialUserDao;
//
// SocialConfigurer implementation methods
//
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
cfConfig.addConnectionFactory(new FacebookConnectionFactory("XXXXXXXXX", "XXXXXXXXX"));
cfConfig.addConnectionFactory(new TwitterConnectionFactory("XXXXXXXXX", "XXXXXXXXX"));
}
#Override
public UserIdSource getUserIdSource() {
return new UserIdSource() {
#Override
public String getUserId() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
}
return authentication.getName();
}
};
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new HibernateUsersConnectionRepository(socialUserDao, connectionFactoryLocator, Encryptors.noOpText());
}
//
// API Binding Beans
//
#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
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Twitter twitter(ConnectionRepository repository) {
Connection<Twitter> connection = repository.findPrimaryConnection(Twitter.class);
return connection != null ? connection.getApi() : null;
}
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public LinkedIn linkedin(ConnectionRepository repository) {
Connection<LinkedIn> connection = repository.findPrimaryConnection(LinkedIn.class);
return connection != null ? connection.getApi() : null;
}
//
// Web Controller and Filter Beans
//
#Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) {
ConnectController connectController = new ConnectController(connectionFactoryLocator, connectionRepository);
connectController.addInterceptor(new PostToWallAfterConnectInterceptor());
connectController.addInterceptor(new TweetAfterConnectInterceptor());
return connectController;
}
#Bean
public ProviderSignInController providerSignInController(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository usersConnectionRepository) {
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository, new SimpleSignInAdapter(new HttpSessionRequestCache()));
}
#Bean
public DisconnectController disconnectController(UsersConnectionRepository usersConnectionRepository, Environment env) {
return new DisconnectController(usersConnectionRepository, env.getProperty("facebook.clientSecret"));
}
#Bean
public ReconnectFilter apiExceptionHandler(UsersConnectionRepository usersConnectionRepository, UserIdSource userIdSource) {
return new ReconnectFilter(usersConnectionRepository, userIdSource);
}
}
Is there any way for me to confirm why the signIn() is not being fired or even if it is being registered.
Related
I inherited a Spring Boot 1.5 project which cannot be migrated up at the moment and need to work on session registry to manage users (ex: list logged users, email users of production updates etc.)
I tried all existing SO solutions I could find, but all of them provide a null result for sessionRegistry().getAllPrincipals(). My security config looks like:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.headers().frameOptions().sameOrigin()
.and()
.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry());
}
#Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
#Bean
public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
}
My Application config looks like:
#EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600000)
#EnableDiscoveryClient
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
#EnableZuulProxy
#EnableFeignClients
#EnableSwagger2
#SpringBootApplication
#RibbonClients({
#RibbonClient(name = "employeeService", configuration = StickySessionEditorRibbonConfiguration.class)
})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public PreFilter preFilter() {
return new PreFilter();
}
#Bean
public PostFilter postFilter() {
return new PostFilter();
}
#Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookieName("JSESSIONID");
serializer.setCookiePath("/");
serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
serializer.setCookieMaxAge(3600000);
return serializer;
}
}
Relevant code to access the session registry looks like this:
public class TestController extends BaseController {
#Autowired
private SessionRegistry sessionRegistry;
...
public List<User> findAllLoggedInUsers() {
final List<Object> allPrincipals = sessionRegistry.getAllPrincipals();
}
}
Using the actuator/bean endpoint I can see that the SessionRegistry Bean is active.
I am logging in successfully from a couple of browsers but allPrincipals is always size 0 before and after the logins.
Am at a loss why, and any help here is much appreciated.
Based on #M.Deinum 's comment regarding disabled login I want to add that the project uses Zuul Filters (preFilter and PostFilter) as indicated in the application config. We have an account-manager service completely different from this api-gateway service, which authenticates users based on simple login/password.
The logic in preFilter looks like this:
public class PreFilter extends BaseFilter {
#Autowired
SessionRegistry sessionRegistry;
#Autowired
private SessionRepository sessionRepository;
#Override
public String filterType() {
return "pre";
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest req = ctx.getRequest();
HttpSession session = req.getSession();
try {
String uri = req.getRequestURI();
// user assumed is not authenticated
String authToken = null;
//Login code
if (uri.contains("/api/public/authorization/login") && req.getMethod().equals("POST")) {
session.removeAttribute(AUTH_TOKEN_HEADER);
LoginRequest request = createLoginRequest(req);
/* LoginRequest basically contains "userName" and "password" entered by user */
ResponseEntity<MessageWrapper<String>> response = accountManagerFeignClient.authenticate(loginRequest);
authToken = response.getBody().getData();
if (authToken != null) {
session.setAttribute(AUTH_TOKEN_HEADER, authToken);
ctx.setResponseStatusCode(HttpStatus.OK.value());
ctx.setSendZuulResponse(false);
return null;
}
// authToken == null implies the user was not authenticated by accountManager
} else if ("internal or public apis are called, they won't need authentication") {
// user remains unauthenticated, which is fine for public or internal apis
return null;
} else {
// Assume this is a protected API and provide authToken if one exists
authToken = (String) session.getAttribute(AUTH_TOKEN_HEADER);
}
if (authToken == null)
throw new Exception(UNAUTHORIZED + ". Log String: " + logString);
// Validated user will go through here
ctx.addZuulRequestHeader(AUTH_TOKEN_HEADER, authToken);
} catch (Exception ex) {
ctx.setResponseBody(UNAUTHORIZED);
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
ctx.setSendZuulResponse(false);
}
return null;
}
}
The only relevant logic in postFilter (similar to preFilter) disables sessions during logout in this manner:
if (authToken != null) {
session.removeAttribute(AUTH_TOKEN_HEADER);
session.removeAttribute(StickySessionEditorRule.STICKY_ID);
session.removeAttribute(StickySessionWSGRule.STICKY_ID);
ctx.setResponseBody(LOGGED_OUT);
ctx.setResponseStatusCode(HttpStatus.OK.value());
ctx.setSendZuulResponse(false);
}
session.invalidate();
My other time-consuming option would be to use the HTTPSessionBindingListener as shown here . I have not tried that yet.
Finally, if none of the above work, how could I work directly with redis and do a findAll() ? It looks like there is a SessionRepository, but I could not find a documented way of using it.
Thank You.
I have implemented WebSocket mechanism in spring boot application.
#Configuration
#EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
private String webSockerClientUri;
private WebSocketHandlerRegistry webSocketHandlerRegistry;
private WebSocketHandler webSocketHandler;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
this.webSocketHandlerRegistry = webSocketHandlerRegistry;
this.webSocketHandler = registerWebSocketHandler();
this.webSocketHandlerRegistry.addHandler(this.webSocketHandler, "/test");
}
public WebSocketHandler registerWebSocketHandler(){
return new WebSocketHandler();
}
public WebSocketHandlerRegistry getWebSocketHandlerRegistry() {
return webSocketHandlerRegistry;
}
public WebSocketHandler getWebSocketHandler() {
return webSocketHandler;
}
}
During startup appp WebSocketHandlerMapping detect registration of Websocket connection "/test". I would like to do the same action in REST endpoint like this.
#PostMapping("/{uri}")
public ResponseEntity register(#PathVariable("uri") String uri){
if (uri != null){
this.webSocketConfiguration.getWebSocketHandlerRegistry().addHandler(webSocketConfiguration.getWebSocketHandler(), "/" + uri);
return new ResponseEntity(HttpStatus.OK);
}
else {
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
}
Uri is added into WebSocketHandlerRegistry however mechanism WebSocketHandlerMapping doesn't detect that fact, because it is not startup state of app. How can I solve this problem?
I want spring-vault configuration marked with VaultPropertySource to be able to retry the requests to the vault if they fail.
What should i mark as retryable ? I'm using Spring-Retry and i was looking over http://www.baeldung.com/spring-retry .
There is no visible method to mark as retryable. Should I change the implementation of the vaultTemplate and mark the vaultOperations as retryable ?
ProvisioningSecrets.java
#Configuration
#VaultPropertySource(
value="secret/provisioning",
propertyNamePrefix = "provisioning.",
renewal = Renewal.RENEW
)
#EnableRetry
#Lazy
#Profile("!test")
public class ProvisioningSecrets {
private static final Logger logger = LoggerFactory.getLogger(ProvisioningSecrets.class);
#Autowired
public void setPassword(#Value("${provisioning.password}") final String password) throws Exception {
logger.info("We successfully set the provisioning db password.");
EnvVars.changeSetting(Setting.PROVISIONING_PASS, password);
}
#Autowired
public void setHost(#Value("${provisioning.host}") final String host) throws Exception {
logger.info("We successfully set the provisioning db host.");
EnvVars.changeSetting(Setting.PROVISIONING_HOST, host);
}
#Autowired
public void setPort(#Value("${provisioning.port}") final int port) throws Exception {
logger.info("We successfully set the provisioning db port.");
EnvVars.changeSetting(Setting.PROVISIONING_PORT, Integer.toString(port));
}
#Autowired
public void setUsername(#Value("${provisioning.username}") final String username) throws Exception {
logger.info("We successfully set the provisioning db username.");
EnvVars.changeSetting(Setting.PROVISIONING_USER, username);
}
#Autowired
public void setDbName(#Value("${provisioning.name}") final String name) throws Exception {
logger.info("We successfully set the provisioning db name.");
EnvVars.changeSetting(Setting.PROVISIONING_DB_NAME, name);
}
}
VaultConfiguration.java
#Configuration
#Profile("!test")
public class VaultConfiguration extends AbstractVaultConfiguration {
private static final Logger logger = LoggerFactory.getLogger(VaultConfiguration.class);
private URI vaultHost;
private String vaultToken;
/**
* Configure the Client Authentication.
*
* #return A configured ClientAuthentication Object.
* #see ClientAuthentication
*/
#Override
public ClientAuthentication clientAuthentication() {
// testing out environment variable value injection
logger.debug("Vault Token configuration done.");
return new TokenAuthentication(vaultToken);
}
#Override
#Bean
#DependsOn("vaultToken")
public SessionManager sessionManager() {
return super.sessionManager();
}
#Override
public SslConfiguration sslConfiguration() {
logger.info("Configuring Vault SSL with NONE.");
return SslConfiguration.NONE;
}
/**
* Specify an endpoint for connecting to Vault.
*
* #return A configured VaultEndpoint.
* #see VaultEndpoint
*/
#Override
public VaultEndpoint vaultEndpoint() {
logger.debug("Vault Host:" + vaultHost.toString());
if (vaultHost.toString().isEmpty()) {
logger.info("Creating default Vault Endpoint.");
return new VaultEndpoint();
}
logger.info("Creating Vault Endpoint based on address: " + vaultHost.toString());
final VaultEndpoint endpoint = VaultEndpoint.from(vaultHost);
logger.info("Created Vault Endpoint: " + endpoint.toString());
return endpoint;
}
#Bean("vaultHost")
public URI vaultHost(#Value("${spring.vault.host}") final URI vaultHost) {
this.vaultHost = vaultHost;
return vaultHost;
}
#Override
#Bean
#DependsOn("vaultHost")
public VaultTemplate vaultTemplate() {
return super.vaultTemplate();
}
#Bean("vaultToken")
public String vaultToken(#Value("${spring.vault.token}") final String vaultToken) {
this.vaultToken = vaultToken;
return vaultToken;
}
}
How about creating a custom VaultTemplate bean class using RetryTemplate?
public class RetryableVaultTemplate extends VaultTemplate {
private final RetryTemplate retryTemplate;
public RetryableVaultTemplate(VaultEndpointProvider endpointProvider,
ClientHttpRequestFactory clientHttpRequestFactory,
SessionManager sessionManager, RetryTemplate retryTemplate) {
super(endpointProvider, clientHttpRequestFactory, sessionManager);
this.retryTemplate = retryTemplate;
}
#Override
public VaultResponse read(final String path) {
return retryTemplate
.execute(new RetryCallback<VaultResponse, RuntimeException>() {
#Override
public VaultResponse doWithRetry(RetryContext context) {
System.out.println("doWithRetry");
return RetryableVaultTemplate.super.read(path);
}
});
}
#Override
public <T> VaultResponseSupport<T> read(final String path, final Class<T> responseType) {
return retryTemplate
.execute(new RetryCallback<VaultResponseSupport<T>, RuntimeException>() {
#Override
public VaultResponseSupport<T> doWithRetry(RetryContext context) {
return RetryableVaultTemplate.super.read(path, responseType);
}
});
}
}
Make sure to register this bean class as vaultTemplate bean instead of VaultTemplate.
Currently I am using apache shiro authentication in my spring boot project. I wrote Custom Realm for Cassandra DB. While autowiring the class inside realm object returns null when submitting login details. My Application Config(#component annotation used):
#Bean(name = "realm")
#DependsOn("lifecycleBeanPostProcessor")
public ApplicationRealm realm() {
ApplicationRealm realm = new ApplicationRealm();
realm.init();
return realm;
}
#Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
My Application Realm Class :
#Configuration
#ComponentScan("com.scm.auth")
public class ApplicationRealm extends AuthorizingRealm {
#Autowired
IAuthRepository authRepo;
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Set<String> roles = new HashSet<String>();
try {
roles.add("admin");
} catch (Exception rEx) {
throw new AuthorizationException(rEx);
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
info.setRoles(roles);
return info;
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
SimpleAuthenticationInfo info = null;
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
User user = authRepo.findByUserName(upToken.getUsername(), true); // my model class
try {
if (user.getCurrentPwd().equals(upToken.getPassword())) {
info = new SimpleAuthenticationInfo(user, user.getCurrentPwd(), getName());
} else {
throw new AuthenticationException("Login name [" + upToken.getUsername() + "] not found!");
}
} catch (Exception idEx) {
throw new AuthenticationException(idEx);
}
return info;
}
Is any annotation missed?
Seems like the you have configured the mismatched annotation. Your ApplicationRealm.java should no have #Configuration annotation. #Component is enough for this Custome Realm.
/*#Configuration*/
#ComponentScan("com.scm.auth")
public class ApplicationRealm extends AuthorizingRealm{
/**/
}
I have a server that is just an API endpoint, no client front-end, no jsp, no html. It uses Spring Boot and I'm trying to secure it with Shiro. The relevent parts of my SpringBootServletInitializer look like this. I'm trying to get Shiro to return a 403 response if it fails the roles lookup as defined in BasicRealm. Yet it seems to default to redirecting to a non-existent login.jsp and no matter what solution I seem to use. I can't override that. Any help would be greatly appreciated.
#SpringBootApplication
public class RestApplication extends SpringBootServletInitializer {
...
#Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter() {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
Map<String, String> filterChain = new HashMap<>();
filterChain.put("/admin/**", "roles[admin]");
shiroFilter.setFilterChainDefinitionMap(filterChain);
shiroFilter.setSecurityManager(securityManager());
return shiroFilter;
}
#Bean
public org.apache.shiro.mgt.SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());
CookieRememberMeManager rmm = new CookieRememberMeManager();
rmm.setCipherKey(Base64.decode("XXXXXXXXXXXXXXXXXXXXXX"));
securityManager.setRememberMeManager(rmm);
return securityManager;
}
#Bean(name = "userRealm")
#DependsOn("lifecycleBeanPostProcessor")
public BasicRealm userRealm() {
return new BasicRealm();
}
#Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
public class BasicRealm extends AuthorizingRealm {
private static Logger logger = UserService.logger;
private static final String REALM_NAME = "BASIC";
public BasicRealm() {
super();
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken token)
throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String userid = upToken.getUsername();
User user = Global.INST.getUserService().getUserById(userid);
if (user == null) {
throw new UnknownAccountException("No account found for user [" + userid + "]");
}
return new SimpleAuthenticationInfo(userid, user.getHashedPass().toCharArray(), REALM_NAME);
}
#Override
protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principals) {
String userid = (String) principals.getPrimaryPrincipal();
if (userid == null) {
return new SimpleAuthorizationInfo();
}
return new SimpleAuthorizationInfo(Global.INST.getUserService().getRoles(userid));
}
}
OK, here is how I solved it. I created a class ...
public class AuthFilter extends RolesAuthorizationFilter {
private static final String MESSAGE = "Access denied.";
#Override
protected boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws IOException {
HttpServletResponse httpResponse ;
try {
httpResponse = WebUtils.toHttp(response);
}
catch (ClassCastException ex) {
// Not a HTTP Servlet operation
return super.onAccessDenied(request, response) ;
}
if (MESSAGE == null) {
httpResponse.sendError(403);
} else {
httpResponse.sendError(403, MESSAGE);
}
return false; // No further processing.
}
}
... and then in my shiroFilter() method above I added this code ...
Map<String, Filter> filters = new HashMap<>();
filters.put("roles", new AuthFilter());
shiroFilter.setFilters(filters);
... hope this helps someone else.
In Shiro 1.4+ you can set the login url in your application.properties:
https://github.com/apache/shiro/blob/master/samples/spring-boot-web/src/main/resources/application.properties#L20
Earlier versions you should be able to set ShiroFilterFactoryBean.setLoginUrl("/login")
https://shiro.apache.org/static/current/apidocs/org/apache/shiro/spring/web/ShiroFilterFactoryBean.html