Spring Security 2.0 Facebook Connect - spring

I´m trying to implement Facebook login to our Spring Framework 2.5 based application which has usual login/password authentication already.
I got to this part of code in my controller where i have user´s Facebook data in userFb object:
String code = request.getParameter("code");
String accessToken = fbStuffKeeper.retrieveAccessToken(code);
facebookClient = new DefaultFacebookClient(accessToken);
User userFb = facebookClient.fetchObject("me", User.class);
I can check if user is already in DB and if not I am able to store him in a DB and store his FB_uid and other necessary credentials.
Problem is that I want to login this user after some condition if he is already authorized to system.
My current authentication manager decides by this rule:
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username, password, confirmed, 1 AS enabled FROM person WHERE confirmed=1 and username=?"
authorities-by-username-query="select username, authority from person where username=?"/>
<password-encoder hash="md5"/>
</authentication-provider>
So my question is: How do I login user found in DB who tries to connect using Facebook? Another authentication provider? Is there any way to do his login using some similar code as is mine mentioned one?
Thank you very much.
***EDIT:
Thank you very much. It looks it might help. I have now one user in DB which I want to login. I made this code in the controller:
User userFb = facebookClient.fetchObject("me", User.class);
Person person = personDao.getPersonByFbUid(userFb.getId());
GrantedAuthority[] grantedAuthorities = new GrantedAuthorityImpl[1];
grantedAuthorities[0] = new GrantedAuthorityImpl(person.getAuthority());
Authentication a = new FacebookAuthenticationToken(grantedAuthorities, person);
a.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(a);
I also created FacebookAuthenticationToken:
public class FacebookAuthenticationToken extends AbstractAuthenticationToken {
private Person person;
public FacebookAuthenticationToken(GrantedAuthority[] authorities, Person person) {
super(authorities);
this.person = person;
}
public Object getCredentials() {
return person;
}
public Object getPrincipal() {
this.person;
} /* + Person getter and setter */
And all works fine now. Thank you once more for your help.

One possible way is following. Although it may not be perfect, but it works well.
Create a FacebookAuthenticationToken class, that extends AbstractAuthenticationToken and holds your facebook user object (assuming it to be FacebookUser).
After your code you mentioned above, append the following code right after it into the same controller:
String code = request.getParameter("code");
String accessToken = fbStuffKeeper.retrieveAccessToken(code);
facebookClient = new DefaultFacebookClient(accessToken);
User userFb = facebookClient.fetchObject("me", User.class);
// Find FacebookUser with current FB_uid, create new if found none, load its information in an instance of FacebookUser
Authentication a = new FacebookAuthenticationToken(fbUserObject);
a.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(a);
// your authentication is complete, redirect to some other page

Related

Get Logged-in User

Please bare with me as I am learning Spring Data REST as I go. Definitely feel free to suggest a safer approach if what I am proposing here is not the safest approach or even possible.
Problem
A user logs into my Spring Data REST API via Google OAuth2. After logging in, I need to get the user's ID, which is just the value of their primary key, from the User table. The reason I need the ID is to restrict access to endpoints such as /users/{id}. If a user's ID is 1, then he is only allowed to view /users/1, unless he is an Administrator.
Current Login Architecture
This portion of the application works as expected:
OAuth2AuthenticationSuccessHandler.java:
#Component("oauth2authSuccessHandler")
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Autowired
private UserDataRestRepository userRepository;
#Autowired
private RandomStringGenerator randomStringGenerator;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) authentication;
String email = authenticationToken.getPrincipal().getAttributes().get("email").toString();
if (!userRepository.existsByEmail(email)) {
// If email not found, create local user account.
String firstName = CaseUtils.toCamelCase(
authenticationToken.getPrincipal().getAttributes().get("given_name").toString().toLowerCase(),
true);
String lastName = CaseUtils.toCamelCase(
authenticationToken.getPrincipal().getAttributes().get("family_name").toString().toLowerCase(),
true);
// Generate temporary username.
BigInteger usernameSuffix = userRepository.getNextValSeqUserUsername();
String username = "User" + usernameSuffix;
// Generate temporary password.
String password = randomStringGenerator.generate(20).toUpperCase();
// Encode Password.
String encodedPassword = passwordEncoder.encode(password);
User newUser = new User();
newUser.setFirstName(firstName);
newUser.setLastName(lastName);
newUser.setUsername(username);
newUser.setUserSetUsername(false);
newUser.setEmail(email);
newUser.setPassword(encodedPassword);
newUser.setUserSetPassword(false);
newUser.setCreated(new Timestamp(System.currentTimeMillis()));
newUser.setDisabled(false);
newUser.setLocked(false);
userRepository.save(newUser);
}
// Redirect to root page.
redirectStrategy.sendRedirect(request, response, "/");
}
}
UserDataRestRepository.java:
#RepositoryRestResource(collectionResourceRel = "users", path = "users")
public interface UserDataRestRepository extends PagingAndSortingRepository<User, Integer>, CrudRepository<User, Integer> {
public boolean existsByEmail(String email);
#Query(value="SELECT NEXT VALUE FOR Seq_User_Username", nativeQuery=true)
public BigInteger getNextValSeqUserUsername();
}
OAuth2LoginSecurityConfig.java:
#Configuration
#EnableWebSecurity
public class OAuth2LoginSecurityConfig {
#Autowired
private AuthenticationSuccessHandler oauth2authSuccessHandler;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> authorize
.anyRequest()
.authenticated()
)
.oauth2Login()
.successHandler(oauth2authSuccessHandler);
return http.build();
}
}
Database Tables
User:
Role:
UserRole:
Current Post-authentication Problems
Currently, after a user logs in via OAuth2, we do not have access to the user's ID (which is the value of their primary key in the User table) unless we access the Authentication object, get the email address, then get the user's ID from the User table based on the email address. Basically: Select Id from User where Email = user#example.com
Proposed Login Architecture
I am wondering if the UserDetailsService can solve the problem, illustrated in green:
My train of thought is: by getting the user's info from the User table, then loading it into the UserDetailsService, the entire application now has access to the user's ID (primary key) and all of the other info in that row via the UserDetailsService.
Thanks for your help.
I would not include a round-trip to the DB for each incoming request on resource-server(s), this is a waste of resources.
Have you considered using an authorization-server in front of Google and either directly bound to your user database, or capable of calling a web-service to fetch additional data? Many propose it, along with "login with Google".
You would have complete control of access and ID tokens claims: you can add about anything as private claim, but the request to the DB (or Web-Service) happens only once per token issuance (and not once per request to a resource-server). And if you define an Authentication of your own (easy, just extend AbstractAuthenticationToken), you can put accessors to cast those private claims from Object to something more relevant to your security / domain model.
I demo something similar (add a private claim to access-token with a value returned by a web-service and then use it for access-control with custom Authentication and security DSL) in this project.

What is the best way accessing username from request

I am developing a simple todo application with Spring Boot. I am using JWT for authorisation. All todos belongs to a user will be get with following endpoint.
#GetMapping("")
public List<Todo> todos(){
//get username from token
//return todos belongs to user
}
I have two problems in here:
Is getting username from token a good practise?
What is the best way accessing username with token from a controller class or a service class?
This answer shows how to reach token from controller class.
but what if we want to reach token from service class? This answer shows it. But the class is deprecated.
you can get username like this:
UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication()
.getPrincipal();
String username = userDetails.getUsername();

Spring Security - How to get authenticated object in non-secure and secure pages?

I have a Spring-boot/Thymeleaf application with two ends point:
1: /int/: requires sso/authorization;
2. /ext/: public pages, everyone can access;
Using a PreAuthenticationFilter, I was able to secure /int/* pages. When an user tries to access the /ext/* pages, I'd like to be able to tell in the controller if the user has previously been authenticated (by accessing a secured page). Currently I save the authenticated Principal object in the HTTP session in UserDetailsService's loadUserDetails(). Just curious if this is the right way (or a better way) to do it.
You can get your authenticated object via #AuthenticationPrincipal annotation instead of getting the object from httpsession and casting it back to your object for every controller method.
Let me give you an example, given login page is a public page and User object as below:
User Class:
public class User implement UserDetails {
String contact;
Integer age;
}
Controller:
#GetMapping(value = "/login")
ModelAndView login(#AuthenticationPrincipal User user) {
if (user == null) {
return new ModelAndView("/login");
} else {
return new ModelAndView(new RedirectView("/home"));
}
}

Spring security grails plugin

I'm using Spring security grails plugin v2.0.0 RC5 in my grails application , but i noticed something , that while login to the application the username is not case sensitive for instance if you wrote user or USER both will login successfully. What i need to do is to make the username case sensitive.
i found the that isLoggedIn action in springSecurityService handling the login but i can't see anything in it any checking of the provided username or password.
here is the isLoggedIn code :
boolean isLoggedIn() {
def authentication = SCH.context.authentication
authentication && !authenticationTrustResolver.isAnonymous(authentication)
}
Am i searching the the right place ?
There is a configuration property for that. https://grails-plugins.github.io/grails-spring-security-core/3.2.x/index.html#domainClassProperties
userLookup.usernameIgnoreCase
In my Grails app I'm using the Spring Security plugin and have defined a custom userDetailsService Spring bean in order to control how user and role data is retrieved, e.g.
class MyUserDetailsService implements GrailsUserDetailsService {
/**
* Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least one role, so
* we give a user with no granted roles this one which gets past that restriction but
* doesn't grant anything.
*/
static final List NO_ROLES = [new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
UserDetails loadUserByUsername(String username, boolean loadRoles) {
return loadUserByUsername(username)
}
UserDetails loadUserByUsername(String username) {
User.withTransaction { status ->
User user = User.findByUsername(username)
if (!user && user.username.equal(username)) {
throw new UsernameNotFoundException('User not found', username)
}
def authorities = user.authorities.collect {new GrantedAuthorityImpl(it.authority)}
return new CustomUserDetails(
user.username,
user.password,
user.enabled,
!user.accountExpired,
!user.passwordExpired,
!user.accountLocked,
authorities ?: NO_ROLES,
user.id,
user.name)
}
}
}
Now you will see
**if (!user && user.username.equal(username)) {**
this will solve your problem.
But here is another problem, your implementation also depends on your database because like MySQL is case insensitive, so if you search a user with name Joy then it will return all the user doesn't wich have name joy doesn't matter it is in capital letter or small letter.
So you need check in database value before persisting new user.
also, another problem is, if you will check the user domain
username blank: false, unique: true, email: true
username is unique, means you can't insert username joy again if Joy exists in db, so you need to change it and write your own custom logic to handle this problem.

Spring Social, Create connection for existing local users on signin

I am trying to allow existing local users to signin using Spring Social, primarily google, and automatically create a new userconnection if email matches local account.
I have only found examples for implicit signup (requires/creates a new account), new user signup (default action if SpringSocial fails to find a UserConnection) and the connect option while the current user is logged in.
How can I extend SpringSocial to let me check for existing social user / local user email match, create a user connection to existing local user and then sign them into both local and social?
You can use the implicit sign up to find an account instead of creating a new one.
Modifying the code found for implicit sign up, you can do something similar to the following:
public class AccountConnectionSignUp implements ConnectionSignUp {
private final AccountRepository accountRepository;
public AccountConnectionSignUp(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public String execute(Connection<?> connection) {
UserProfile profile = connection.fetchUserProfile();
Account account = accountRepository.findAccountByEmail(profile.getEmail());
return account == null ? null : account.getUsername();
}
}
You will need to implement Account and AccountRepository to support an email property, then use the repository to find account by email. If the repository cannot find an account, return null.

Resources