I have to authenticate a SOAP message that sends passwords in plain text with the encrypted password from the database. We use HTTPS for all traffic so the entire message in encrypted.
Anyway, I have implemented the following SI configuration, which works until Spring tries to match the passwords. Since the SOAP password is plain text and the db passowrd is encrypted, I must encrypt the password before Spring authenticates. I have service methods to do this already implemented.
<bean id="SOAPSecurityValidationCallbackHandler" class="org.springframework.ws.soap.security.wss4j.callback.SpringSecurityPasswordValidationCallbackHandler">
<property name="userDetailsService" ref="userDetailsService"/>
</bean>
<bean id="userDetailsService" class="com.ps.snt.ws.security.SNTUserDetailsService" />
UserDetailsService implementation:
public class SNTUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("In security callback " + username);
boolean valid = true;
Integer zoneID = null;
StringBuffer errorMessages = new StringBuffer();
if(StringUtils.isEmpty(username)) {
// TODO wil lthis ever happen, does framework check first?
errorMessages.append("Username token cannot be empty");
valid = false;
} else {
Pattern pattern = Pattern.compile("^[\\w]+#+\\d\\d\\d\\d\\d");
Matcher matcher = pattern.matcher(username);
if(!matcher.matches()) {
valid = false;
errorMessages.append("Username token must be in the format 'user#zone'.");
}
else {
String[] parts = username.split("#");
username = parts[0];
zoneID = Integer.parseInt(parts[1]);
}
}
if(valid && username != null && zoneID != null) {
LoginService loginService = new LoginService();
ApplicationUserDO user = loginService.getUserByUsername(zoneID.toString(), username);
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new GrantedAuthorityImpl("ROLE_USER"));
UserDetails userDetails = new User(user.getUsername(),
user.getPassword(),
enabled,
accountNonExpired,
credentialsNonExpired,
accountNonLocked,
grantedAuths );
return userDetails;
} else {
System.out.println("Authetnication failed!");
throw new UsernameNotFoundException("Epic fail!");
}
}
}
The user details service does what its supposed to - it returns a user with the password from the database.
I tried overidding handleUSernameTokenPrincipal in SpringSecurityPasswordValidationCallbackHandler to set the password to the encrypted version, but this method never gets called. Overriding handleUsername is not useful as I do not have access to the original password.
I would like to keep the details service and use this callback, but I need to set the password in the principal to the encrypted version before authentication.
Ideally I would want to set the password after validation of the SOAP header but before the user details service is called. Not sure if I can do this with another interceptor, but I do not want to parse through XML and et it that way. I would like to do it in a somewhat safe manner.
I found a solution using an authentication manager on the callback:
<bean id="SOAPSecurityValidationCallbackHandler"
class="org.springframework.ws.soap.security.xwss.callback.SpringPlainTextPasswordValidationCallbackHandler">
<property name="authenticationManager" ref="CommonAuthenticationManager" />
</bean>
The AuthenticationManager interface must implement one method - authenticate:
public class CommonAuthenticationManager implements AuthenticationManager {
public Authentication authenticate(Authentication auth)
throws AuthenticationException {
String username = auth.getName();
String password = (String) auth.getCredentials();
... encrypt password
... validate against DB
// this will be available in the message flow to any channel
return new UsernamePasswordAuthenticationToken(auth.getName(),
auth.getCredentials(), grantedAuths);
}
}
Related
I have a controller that authorizes user:
#PostMapping
public ResponseEntity<Token> signInUser(Credentials credentials) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
credentials.getUsername(),
credentials.getPassword())
);
Token token = tokenService.generateToken(authentication);
return ResponseEntity.ok(token);
}
"Credentials" is just a simple data object, generated by swagger:
Credentials
AuthenticationManager bean:
#Bean
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService) {
var authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
return new ProviderManager(authProvider);
}
As I understand, AuthenticationManager only retrieves user from the database and checks if passwords match. And I would like to write all this logic myself. Do I have to write my own implementation of AuthenticationManager or I can just write UserService that will not implement anything?
For example, like these:
UserController:
#PostMapping
public ResponseEntity<Token> signInUser(Credentials credentials) {
Token token = userService.signIn(credentials);
return ResponseEntity.ok(token);
}
UserService:
public Token signIn(Credentials credentials) {
Optional<UserEntity> userEntityOpt =
userEntityRepo.findByUsername(credentials.getUsername());
return tokenService.generateToken(userEntityOpt.get());
}
Validation and stuff omitted
I have been able to get a Spring Security based application up and running, and it has been satisfying all my requirements until now.
I do have 1 doubt regarding how UserDetailsService is used in Spring Security. I have a custom 'UserDetailsService' implementation, which goes like this -
public class CustomUserDetailsService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserDetailsDto> userDetailsByEmail = // Load userDetailsDto from database
if (!userDetailsByEmail.isPresent()) {
throw new UsernameNotFoundException("Username does not exists");
}
UserDetailsDto userDetailsDto = userDetailsByEmail.get();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
return new LoggedInUser(userDetailsDto, roles, modulePermissions, userType);
}
}
The class LoggedInUser is an extension of Spring Security's org.springframework.security.core.userdetails.User class, which goes like this -
public class LoggedInUser extends User {
private static final long serialVersionUID = -1L;
private Long userId;
private boolean firstLogin;
private UserType userType;
private List<ModulePermission> modulePermissions;
private String firstName;
private String lastName;
private String contactNo;
public LoggedInUser(UserDetailsDto userDetailsDto, List<Role> roles, List<ModulePermission> modulePermissions,
UserType userType) {
super(userDetailsDto.getEmail(), userDetailsDto.getPassword(), userDetailsDto.getEnabledStatus().getValue(),
userDetailsDto.getAccountNonExpiredStatus().getValue(), true,
userDetailsDto.getAccountNonLockedStatus().getValue(),
roles.stream().map(role -> new SimpleGrantedAuthority(role.getId())).collect(Collectors.toList()));
this.modulePermissions = modulePermissions;
this.userType = userType;
this.userId = userDetailsDto.getId();
this.firstLogin = userDetailsDto.getIsFirstLoginStatus().getValue();
this.firstName = userDetailsDto.getFirstName();
this.lastName = userDetailsDto.getLastName();
this.contactNo = userDetailsDto.getContactNo();
}
public List<ModulePermission> getModulePermissions() {
return Collections.unmodifiableList(modulePermissions);
}
public UserType getUserType() {
return userType;
}
public Long getUserId() {
return userId;
}
public boolean isFirstLogin() {
return firstLogin;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getContactNo() {
return contactNo;
}
public void setFirstLogin(boolean firstLogin) {
this.firstLogin = firstLogin;
}
}
Now, to configure Spring Security to use my CustomUserDetailsService, I do the following in security configuration -
#Bean
public UserDetailsService customUserDetailsService() {
return new CustomUserDetailsService();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher(SuperAdminConstant.UrlConstant.ANT_MATCHER_PATH)
.userDetailsService(customUserDetailsService())
.formLogin(// further configuration)
}
And this works without any problems.
But notice that in CustomUserDetailsService, several database queries are executed even before the user has been authenticated successfully (This is because Spring Security has created a DaoAuthenticationProvider, which loads a UserDetails implementation (in my case, LoggedInUser), and perform various checks on that object AFTER it has been retrieved from a UserDetailsService (in my case , CustomUserDetailsService)).
Consider that a user has entered the correct username, but a wrong password. In that case, the high-level authentication flow would be -
CustomUserDetailsService would be called
First query is executed to verify username and load user details (UsernameNotFoundException is not thrown as username is correct)
Second query is executed to retrieve the roles
Third query is executed to retrieve module permissions
Fourth query is executed to retrieve user types
DaoAuthenticationProvider checks the password, finds it to be incorrect, and throws a BadCredentialsException.
So as can be seen, total 4 queries are executed EVEN BEFORE authentication process has completed, out of which only 1 is essential at this stage (the first query to verify username).
One solution to this problem can be to eliminate the use of UserDetailsService altogeather, and use a custom AuthenticationProvider instead.
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Override
Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// Customize the authentication logic here, and retrieve
// user information only if everything is correct.
}
}
But going by this approach also means that I have to duplicate code and functionality provided by DaoAuthenticationProvider and AbstractUserDetailsAuthenticationProvider, which includes checking user account status flags manually (accountNonExpired, accountNonLocked etc.), and throwing exceptions.
So I was wondering weather it is possible to perform authentication logic in such a way that user information is retrieved only AFTER authentication succeeds, and most of the authentication logic provided by Spring Security can be resused.
Any ideas will be deeply appreciated.
You can write implementation of AuthenticationSuccessHandler:
#Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
LoggedInUser loggedInUser = (LoggedInUser)authentication.getPrincipal();
List<Role> roles = roleService.listByEmail(username);
List<ModulePermission> modulePermissions = modulePermissionService.listByUserId(userDetailsDto.getId());
UserType userType = userTypeService.getByUserId(userDetailsDto.getId());
// Set roles after authentication succeeds
loggedInUser.setRoles(roles);
loggedInUser.setModulePermissions(modulePermissions);
loggedInUser.setUserType(userType);
}
}
After the authentication succeeds, you can obtain logged in user from security context and set additional properties.
We are in the process of developing a in house mobile application and web api.
We are using asp.net web api 2 with asp.net Identy 2 OAuth.
I have got the api up and running and giving me a bearer token. However I want to slightly modify the process flow to something like along the lines of this:
App user logs in to api with username and password.
App receives Refresh-token which is valid for 30 days.
App then requests an access token providing the api with the refresh token. ( Here I want to be able to invalidate a request if the user has changed their password or their account has been locked).
App Gets An Access token which is valid for 30 minutes or it gets a 401 if the password check failed.
The App can access the api with the given access token for the next 29 minutes. After that the app will have to get a new access token with the refresh token.
Reason I want to do this is in order to stop a users devices gaining access to the api after they have changed their password. If their phone gets stolen they need to be able to login to the website and change their password so the new owner of the phone cannot gain access to our companies services.
Is my proposed solution do able, and if so is it a sensible solution? I haven't forgotten any crucial elements?
I am willing to do a db access on ever token refresh, but not on every API call.
To summarize my questions are:
Is my planned method sensible?
How would I securely check if the password has changed or if account is locked in the refresh token process.
Please find my current OAuth Setups and classes below: (I have tried to add the refresh token functionality, but have not attempted to add any password verification yet)
Startup.Auth.cs
public partial class Startup
{
public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }
public static string PublicClientId { get; private set; }
// For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
// Configure the db context and user manager to use a single instance per request
app.CreatePerOwinContext(IdentityDbContext.Create);
app.CreatePerOwinContext<FskUserManager>(FskUserManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
// Configure the application for OAuth based flow
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
}
ApplicationRefreshTokenProvider.cs
public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
// Expiration time in minutes
int refreshTokenExpiration = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["ApiRefreshTokenExpiry"]);
context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddMinutes(refreshTokenExpiration));
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
ApplicationOAuthProvider.cs
public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
private readonly string _publicClientId;
public ApplicationOAuthProvider(string publicClientId)
{
if (publicClientId == null)
{
throw new ArgumentNullException("publicClientId");
}
_publicClientId = publicClientId;
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var userManager = context.OwinContext.GetUserManager<FskUserManager>();
FskUser user = await userManager.FindAsync(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
OAuthDefaults.AuthenticationType);
ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
CookieAuthenticationDefaults.AuthenticationType);
AuthenticationProperties properties = CreateProperties(user.UserName);
AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
context.Validated(ticket);
context.Request.Context.Authentication.SignIn(cookiesIdentity);
}
public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
{
context.AdditionalResponseParameters.Add(property.Key, property.Value);
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
// Resource owner password credentials does not provide a client ID.
if (context.ClientId == null)
{
context.Validated();
}
return Task.FromResult<object>(null);
}
public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
{
if (context.ClientId == _publicClientId)
{
Uri expectedRootUri = new Uri(context.Request.Uri, "/");
if (expectedRootUri.AbsoluteUri == context.RedirectUri)
{
context.Validated();
}
}
return Task.FromResult<object>(null);
}
public static AuthenticationProperties CreateProperties(string userName)
{
IDictionary<string, string> data = new Dictionary<string, string>
{
{ "userName", userName }
};
return new AuthenticationProperties(data);
}
}
If I understood your task right, here is an idea.
On the create access token event, you can check if the password has been changed from the website and if so, revoke the refresh token. (you can create some flag that the password has been changed or something)
It should not be often when you are creating an access token so there should be no problems with the db access.
Now the question is how to revoke a refresh token. Unless there is a build in way you will have to implement a custom one. An idea here is to check the refresh token creation date and the date of the change password operation. If the change password operation is done after the creation of the refresh token, you do not authenticate the user.
Let me know what you think of this.
i am trying to automatically authorization without login in spring security. The user would be authorized by clicking a link in a website.
I have a class UserLoginService that called from spring-security xml file like this;
<authentication-manager>
<authentication-provider user-service-ref="userLoginService" >
<password-encoder hash="md5"/>
</authentication-provider>
</authentication-manager>
<beans:bean id="userLoginService"
class="tr.com.enlil.formdesigner.server.guvenlik.UserLoginService">
</beans:bean>
UserLoginService class;
public class UserLoginService implements UserDetailsService {
private static Logger logger = Logger.getLogger(InitServlet.class);
#Autowired
private IKullaniciBusinessManager iKullaniciBusinessManager;
/**
* {#inheritDoc}
*/
#Override
public UserDetails loadUserByUsername(String username) {
try {
Kullanici kullanici = new Kullanici();
kullanici.setKullaniciAdi(username);
Kullanici kullaniciBusinessManager = iKullaniciBusinessManager.getirKullaniciAdinaGore(kullanici);
User user = new User();
if (kullaniciBusinessManager != null && kullaniciBusinessManager.getAktifmi()) {
user.setUsername(kullaniciBusinessManager.getKullaniciAdi());
user.setPassword(kullaniciBusinessManager.getSifre());
user.setKullanici(kullaniciBusinessManager);
List<String> yetkiListesi = new ArrayList<String>();
List<GrantedAuthority> grandAuthorities = new ArrayList<GrantedAuthority>();
//TODO yetkilerle alakalı birşey yapmak gerekebilir.
for (String yetki : yetkiListesi) {
GrantedAuthorityImpl g = new GrantedAuthorityImpl(yetki);
grandAuthorities.add(g);
}
user.setAuthorities(grandAuthorities);
}
return user;
} catch (Exception e) {
logger.error("Kullanici alinirken hata olustu!!", e);
}
return null;
}
public static void autoLogin(User user, HttpServletRequest request, AuthenticationManager authenticationManager) {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(),
user.getPassword(), user.getAuthorities());
// generate session if one doesn't exist
request.getSession();
token.setDetails(new WebAuthenticationDetails(request));
Authentication authenticatedUser = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticatedUser);
// setting role to the session
request.getSession().setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
}
}
I found autoLogin method from Make Programmatic login without username/password?. But i dont know, from where can i call this method and will this code help me.
Thanks in advance.
You will have to create your own implementation of AbstractPreAuthenticatedProcessingFilter. The method getPreAuthenticatedPrincipal(HttpServletRequest request) will have the request where you can get your credentials from. You will need to return a subject if it is a valid user or null if it is not. Your implementation of UserDetailsService will transform the subject to a UserDetails object.
i give users special URL with access key in it. users accessing the public page via this special url should be able to see some additional data as compared to simple anonymous user.
i want to give some additional role to anonymous user based on parameters provided in request so i can do something like this in my template:
<#sec.authorize ifAnyGranted="ROLE_ADMIN, ROLE_USER, ROLE_INVITED_VISITOR">
...some additional stuff for invited user to see
</#sec.authorize>
currently i'm implementing Spring's OncePerRequestfilter:
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if (null != request.getParameter("accessKey")) {
if(isValid(request.getParameter("accessKey"))) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
//how do i add additional roles to authenticated (potentially anonymous) user?
}
}
}
Why not just create a wrapper class that delegates to the original, but adds on a couple of extra GrantedAuthorities:
public class AuthenticationWrapper implements Authentication
{
private Authentication original;
private GrantedAuthority[] extraRoles;
public AuthenticationWrapper( Authentication original, GrantedAuthority[] extraRoles )
{
this.original = original;
this.extraRoles = extraRoles;
}
public GrantedAuthority[] getAuthorities()
{
GrantedAuthority[] originalRoles = original.getAuthorities();
GrantedAuthority[] roles = new GrantedAuthority[originalRoles.length + extraRoles.length];
System.arraycopy( originalRoles, 0, roles, 0, originalRoles.length );
System.arraycopy( extraRoles, 0, roles, originalRoles.length, extraRoles.length );
return roles;
}
public String getName() { return original.getName(); }
public Object getCredentials() { return original.getCredentials(); }
public Object getDetails() { return original.getDetails(); }
public Object getPrincipal() { return original.getPrincipal(); }
public boolean isAuthenticated() { return original.isAuthenticated(); }
public void setAuthenticated( boolean isAuthenticated ) throws IllegalArgumentException
{
original.setAuthenticated( isAuthenticated );
}
}
and then do this in your filter:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
GrantedAuthority extraRoles = new GrantedAuthority[2];
extraRoles[0] = new GrantedAuthorityImpl( "Role X" );
extraRoles[1] = new GrantedAuthorityImpl( "Role Y" );
AuthenticationWrapper wrapper = new AuthenticationWrapper( auth, extraRoles );
SecurityContextHolder.getContext().setAuthentication( wrapper );
The Authentication is now replaced by your version with the extra roles. NB You may have to handle the case where the Authentication has not yet been authenticated and so its getAuthorities() returns null. (The wrapper implementation currently assumes that it will always get a non-null array from its wrapped Authentication)