How to toggle disable/enable of shiro authentication? - rest

Is there a way to toggle on-off shiro authentication in my REST service?
I am currently using dropwizard with shiro for my REST services?
Also how can I confirm shiro is disabled?
I also have a shiro.ini file but is there any setting that I can include to toggle shiro authentication?
#Override
protected ShiroConfiguration narrow(T configuration) {
return configuration.getShiroConfiguration();
}
#Override
protected Filter createFilter(T configuration) {
IniWebEnvironment shiroEnv = new IniWebEnvironment();
shiroEnv.setConfigLocations(this.narrow(configuration).iniConfigs());
shiroEnv.init();
return new AbstractShiroFilter() {
public void init() throws Exception {
Collection<Realm> realms = MyShiroBundle.this.createRealms(configuration);
DefaultWebSecurityManager webSecurityManager =
(DefaultWebSecurityManager)shiroEnv.getWebSecurityManager();
if (webSecurityManager.getRealms() != null) {
webSecurityManager.setRealms(mergeRealms(webSecurityManager.getRealms(), realms));
} else {
webSecurityManager.setRealms(realms);
}
this.setSecurityManager(webSecurityManager);
this.setFilterChainResolver(shiroEnv.getFilterChainResolver());
}
};
}
#Override
protected Collection<Realm> createRealms(T configuration) {
return mergeRealms(super.createRealms(configuration), Collections.singleton(createJdbcRealm(configuration)));
}
private static Collection<Realm> mergeRealms(Collection<Realm> realms, Collection<Realm> realms2) {
return ImmutableList.<Realm>builder().addAll(realms).addAll(realms2).build();
}
private static Realm createJdbcRealm(ReferenceDataConfiguration configuration) {
DataSource ds = configuration.getMyDatabaseDataSourceFactory().build(new MetricRegistry(), "my-shiro");
JdbcRealm realm = new JdbcRealm();
realm.setDataSource(ds);
realm.setPermissionsLookupEnabled(false);
realm.setCredentialsMatcher(new AllowAllCredentialsMatcher());
realm.setAuthenticationQuery(
"select UserId from AppUser where IsActive = 1 and UserId = ?");
realm.setUserRolesQuery("select Role from UserRoles where UserId = ? and ValidToTime = " + VALID_TIME);
realm.setPermissionsQuery(
"select Permission from RolesPermissions where Role = ? and ValidToTime = " + VALID_TIME);
return realm;
}

Related

How to Query Database From Startup.CS

I am doing user authentication in my startup.cs. I need to query my database using the OpenIDConnect claims info. This is what I have done but don't know how to get the connection to work. I tried injecting the db query constructor at the top of the startup.cs like this and then calling the query as follows:
public class Startup
{
protected IAdoSqlService _adoSqlService;
public Startup(IConfiguration configuration, IAdoSqlService adoSqlService)
{
Configuration = configuration;
_adoSqlService = adoSqlService;
}
public void ConfigureServices(IServiceCollection services)
{
// do ConfigureServices stuff
options.Events = new OpenIdConnectEvents()
{
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
//How do I call the database to run the following query
int isUser = _adoSqlService.isUser(userntid);
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
}
else
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
}
//More stuff
}
}
When I run the above, it errors in program.cs before even running with the following error
System.InvalidOperationException: 'Unable to resolve service for type 'XXXX.Services.IAdoSqlService' while attempting to activate 'XXXX.Startup'.'
So how do I make the call _adoSqlService.isUser(userntid); to the database?
I am NOT using EF.
Solution
I figured this out by doing the following:
I moved most of my services to the top of the ConfigureServices section (based on something that #qudus said) before I performed my authentication.
I removed the database injection code from the top of the startup.cs.
Lastly I changed the OnTokenValidated to use the following:
ctx.HttpContext.RequestServices.GetRequiredService();
Here is the code:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
internal static IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionSection = Configuration.GetSection("ConnectionStrings");
services.Configure<ConnectionStrings>(connectionSection);
services.AddScoped<IAdoSqlService, AdoSqlService>();
services.AddControllersWithViews();
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSession();
// Load the Federation configuration section from app settings
var federationConfig = Configuration.GetSection("Federation");
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
options.SlidingExpiration = true;// default
options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
})
.AddOpenIdConnect(options =>
{
//Set Options here......
//optional customizations to the auth and failure events
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
// handle an error response from Federation and redirect the user to a custom error page instead
context.Response.Redirect("/Error/401");
context.HandleResponse();
return Task.CompletedTask;
},
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;
int isUser = 0;
int isAdmin = 0;
try
{
var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
isUser = db.isUser(userntid);
isAdmin = db.isAdmin(userntid);
}
catch (Exception ex)
{
string error = ex.Message;
}
AppHttpContext.Current.Session.SetString("IsUser", "false");
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
AppHttpContext.Current.Session.SetString("IsUser", "true");
}
AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
if (isAdmin > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
}
if (isUser == 0 && isAdmin == 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
};
});
Solution
I figured this out by doing the following:
I moved most of my services to the top of the ConfigureServices section (based on something that #qudus said) before I performed my authentication.
I removed the database injection code from the top of the startup.cs.
Lastly I changed the OnTokenValidated to use the following:
ctx.HttpContext.RequestServices.GetRequiredService();
Here is the code:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
internal static IConfiguration Configuration { get; private set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var connectionSection = Configuration.GetSection("ConnectionStrings");
services.Configure<ConnectionStrings>(connectionSection);
services.AddScoped<IAdoSqlService, AdoSqlService>();
services.AddControllersWithViews();
services.AddHttpContextAccessor();
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSession();
// Load the Federation configuration section from app settings
var federationConfig = Configuration.GetSection("Federation");
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromHours(2);//default is 14days
options.SlidingExpiration = true;// default
options.AccessDeniedPath = "/Error/AuthenticateError";// set a custom error access denied error page. this would need to be created/handled in your app.
})
.AddOpenIdConnect(options =>
{
//Set Options here......
//optional customizations to the auth and failure events
options.Events = new OpenIdConnectEvents()
{
OnRedirectToIdentityProvider = context =>
{
return Task.CompletedTask;
},
OnRemoteFailure = context =>
{
// handle an error response from Federation and redirect the user to a custom error page instead
context.Response.Redirect("/Error/401");
context.HandleResponse();
return Task.CompletedTask;
},
OnTokenValidated = async ctx =>
{
// This is the ClaimsIdentity created by OpenID Connect, you can add claims to it directly
ClaimsIdentity claimsIdentity = ctx.Principal.Identities.FirstOrDefault();
string userntid = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "preferred_username").Value;
string username = claimsIdentity.Claims.FirstOrDefault(c => c.Type == "name").Value;
int isUser = 0;
int isAdmin = 0;
try
{
var db = ctx.HttpContext.RequestServices.GetRequiredService<IAdoSqlService>();
isUser = db.isUser(userntid);
isAdmin = db.isAdmin(userntid);
}
catch (Exception ex)
{
string error = ex.Message;
}
AppHttpContext.Current.Session.SetString("IsUser", "false");
if (isUser > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "user"));
AppHttpContext.Current.Session.SetString("IsUser", "true");
}
AppHttpContext.Current.Session.SetString("IsUserAdmin", "false");
if (isAdmin > 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "admin"));
AppHttpContext.Current.Session.SetString("IsUserAdmin", "true");
}
if (isUser == 0 && isAdmin == 0)
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "not authorized"));
}
}
};
});

ASP.NET Core JWT and Claims

I have a question regarding JWT authentication in ASP.NET Core and Claims, because I don't know if I get everything correctly.
When I create a JWT token in ASP.NET I add some Claims, some of which can be custom. What happens when the request with JWT token is sent from the client to API. How is User.Claims filled ? Does it use the claims that are read from JWT?
I would like to create a custom Identity provider ( don't want to use this provided by ASP.NET), with my own tables for user data, roles etc. I don't want store all important data required to fulfill the policy in JWT token (the amount of information stored in token matters, as well as security matters). Is it possible to store only basic claims (like user id, name etc) in JWT token, and then re-fetch other required data DB/ Cache? Along with that, I would like to use the standard mechanism for [Authorize] and the Policy mechanism.
How to make this all work: Custom User Identity + JWT + Standard ASP.NET policy-based authorization + claims fetched from DB/Cache on every request? How to achieve this?
Asp Net Core
First step is write the method that configure Jwt authentication:
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime. As we're creating the tokens locally and validating
// them on the same machines which should have synchronised time,
// this can be set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
};
});
}
Now inside the ConfigureServices() method of the Startup.cs, we can call ConfigureJwtAuthService() method to configure the Jwt authentication.
This is the complete Startup.cs:
using System;
using Autofac;
using ExpertCodeBlogWebApp.Controllers;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.SpaServices.Webpack;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
namespace ExpertCodeBlogWebApp
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add
// services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Configure jwt autenticazione
ConfigureJwtAuthService(services);
// Repositories
services.AddScoped<IUserRepository, UserRepository>();
// Create the Autofac container builder for dependency injection
var builder = new ContainerBuilder();
// Add any Autofac modules or registrations.
builder.RegisterModule(new AutofacModule());
// Return ServiceProvider
var serviceProvider = services.BuildServiceProvider();
return serviceProvider;
}
// Configure authentication with JWT (Json Web Token).
public void ConfigureJwtAuthService(IServiceCollection services)
{
// Enable the use of an [Authorize(AuthenticationSchemes =
// JwtBearerDefaults.AuthenticationScheme)]
// attribute on methods and classes to protect.
services.AddAuthentication().AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = JwtController.SecurityKey,
ValidAudience = JwtController.Audience,
ValidIssuer = JwtController.Issuer,
// When receiving a token, check that we've signed it.
ValidateIssuerSigningKey = true,
// When receiving a token, check that it is still valid.
ValidateLifetime = true,
// This defines the maximum allowable clock skew when validating
// the lifetime.
// As we're creating the tokens locally and validating them on the
// same machines which should have synchronised time, this can be
// set to zero.
ClockSkew = TimeSpan.FromMinutes(0)
};
});
}
// This method gets called by the runtime. Use this method to configure
// the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
// For dependency injection.
public class AutofacModule : Module
{
// Dependency Injection with Autofact
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<UserRepository>().As<IUserRepository>()
.SingleInstance();
}
}
}
The JwtController.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using ExpertCodeBlogWebApp.Domain;
using ExpertCodeBlogWebApp.Domain.Interfaces;
using ExpertCodeBlogWebApp.Domain.Models;
using ExpertCodeBlogWebApp.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
namespace ExpertCodeBlogWebApp.Controllers
{
[Route("api/[controller]")]
public class JwtController : Controller
{
#region Private Members
// JWT-related members
private TimeSpan TokenExpiration;
private SigningCredentials SigningCredentials;
// EF and Identity members, available through DI
private MyDbContext DbContext;
private IUserRepository _userRepository;
private readonly ILogger _logger;
#endregion Private Members
#region Static Members
private static readonly string PrivateKey = "my_PrivateKey";
public static readonly SymmetricSecurityKey SecurityKey =
new SymmetricSecurityKey(Encoding.ASCII.GetBytes(PrivateKey));
public static readonly string Issuer = "my_Issuer";
public static readonly string Audience = "my_Audience";
#endregion Static Members
#region Constructor
// I have used Autofac in the Startup.cs for dependency injection)
public JwtController(
MyDbContext dbContext,
IUserRepository userRepository,
ILogger<JwtController> logger)
{
_logger = logger;
_userRepository = userRepository;
// Instantiate JWT-related members
TokenExpiration = TimeSpan.FromMinutes(10);
SigningCredentials = new SigningCredentials(SecurityKey,
SecurityAlgorithms.HmacSha256);
// Instantiate through Dependency Injection with Autofact
DbContext = dbContext;
}
#endregion Constructor
#region Public Methods
// Manages the request for a new authentication or the refresh of an
// already established one
[HttpPost("token")]
public async Task<IActionResult>
Authentication([FromBody]JwtRequestViewModel jwt)
{
if (ModelState.IsValid)
{
string grantType = jwt.GrantType;
if (grantType == "password")
{
string userName = jwt.UserName;
string password = jwt.Password;
// Password check required
var user = await
_userRepository.GetUserInfoWithCheckPwd(userName, password);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of {user.Name} expired!");
if (UserEnabled(user))
return await GenerateToken(user);
else
return BadRequest("User name or password invalid.");
}
}
else if (grantType == "refresh_token")
{
string userName = jwt.UserName;
// Refresh token (no password check required)
var user = await _userRepository.GetUserInfoByName(userName);
// Check if user is expired (check the ExpireDate property)
if (UserExpired(user))
return BadRequest($"Account of {user.Name} expired!");
string token = jwt.Token;
if (token == user.Token)
{
// Generate token and send it via a json-formatted string
return await GenerateToken(user);
}
else
{
return BadRequest("User token invalid.");
}
}
else
return BadRequest("Authentication type invalid.");
}
else
return BadRequest("Request invalid.");
}
#endregion Public Methods
#region Private Methods
private bool UserExpired(Users utente)
{
if (utente != null)
return utente.ExpireDate.CompareTo(DateTime.Now) < 0;
return true;
}
private bool UserEnabled(Users utente)
{
if (utente != null)
return utente.Enabled == true;
return false;
}
private JsonSerializerSettings DefaultJsonSettings
{
get
{
return new JsonSerializerSettings()
{
Formatting = Formatting.Indented
};
}
}
private async Task<IActionResult> GenerateToken(Users user)
{
try
{
if (user != null)
{
var handler = new JwtSecurityTokenHandler();
DateTime newTokenExpiration = DateTime.Now.Add(TokenExpiration);
ClaimsIdentity identity = new ClaimsIdentity(
new GenericIdentity(user.Name, "TokenAuth"),
new[] { new Claim("ID", user.Id.ToString())}
);
var securityToken = handler.CreateToken(new SecurityTokenDescriptor
{
Issuer = JwtController.Issuer,
Audience = JwtController.Audience,
SigningCredentials = SigningCredentials,
Subject = identity,
Expires = newTokenExpiration
});
string encodedToken = handler.WriteToken(securityToken);
// Update token data on database
await _userRepository.UpdateTokenData(user.Name, encodedToken,
newTokenExpiration);
// Build the json response
// (I use Automapper to maps an object into another object)
var jwtResponse = Mapper.Map<JwtResponseViewModel>(user);
jwtResponse.AccessToken = encodedToken;
jwtResponse.Expiration = (int)TokenExpiration.TotalSeconds;
return Ok(jwtResponse);
}
return NotFound();
}
catch(Exception e)
{
return BadRequest(e.Message);
}
}
#endregion
}
}
On my project I use Angular. For call JwtController method by Angular:
login(userName: string, password: string)
{
return this.getLoginEndpoint(userName, password)
.map((response: Response) => this.processLoginResponse(response));
}
getLoginEndpoint(userName: string, password: string): Observable<Response>
{
// Body
// JwtRequest is a model class that I use to send info to the controller
let jwt = new JwtRequest();
jwt.GrantType = "password";
jwt.UserName = userName;
jwt.Password = password;
jwt.ClientId = "my_Issuer";
// Post requiest (I use getAuthHeader that attach to the header the
// authentication token, but it can also be omitted because it is ignored
// by the JwtController
return this.http.post(this.loginUrl, JSON.stringify(jwt),
this.getAuthHeader(true))
}
protected getAuthHeader(includeJsonContentType?: boolean): RequestOptions
{
// Hera I use this.authService.accessToken that is a my service where
// I have store the token received from the server
let headers = new Headers({
'Authorization': 'Bearer ' + this.authService.accessToken });
if (includeJsonContentType)
headers.append("Content-Type", "application/json");
headers.append("Accept", `application/vnd.iman.v01+json,
application/json, text/plain, */*`);
headers.append("App-Version", "01");
return new RequestOptions({ headers: headers });
}
private processLoginResponse(response: Response)
{
// process the response..
}
On the controllers classes (or methods) that you want to be accessible only by authenticated users (not on your JwtController because its method must be accessible by all users) you can set:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
To call from Angular the controller method that require authentication, you need to attach the token into the header with the getAuthHeader() method.
I hope this post can help you.
yes it uses the claim stored in jwt token
look at the httpcontext object for claims that are stored in token when you created the token
this link can also be helpfull https://joonasw.net/view/adding-custom-claims-aspnet-core-2

Apache shiro remember me not working

Im trying to use the rememberme feature from apache shiro, but its not working.
I have this shiro.ini
[main]
ds = org.apache.shiro.jndi.JndiObjectFactory
ds.requiredType = javax.sql.DataSource
ds.resourceName = java:/comp/env/jdbc/myDS
# JDBC realm config
jdbcRealm = br.com.myproject.web.service.security.JdbcRealmImpl
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.authenticationQuery = SELECT password FROM user WHERE username = ? AND status = 1
jdbcRealm.dataSource = $ds
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
jdbcRealm.credentialsMatcher = $sha256Matcher
securityManager.realms = $jdbcRealm
[urls]
/** = authcBasic
This is my JdbcRealmImpl:
public class JdbcRealmImpl extends JdbcRealm {
public JdbcRealmImpl() {
super();
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(
final AuthenticationToken token) throws AuthenticationException {
final AuthenticationInfo info = super.doGetAuthenticationInfo(token);
final UserDB userDB = new UserDB();
final User user = userDB.getUserByUsername((String) token.getPrincipal());
return new SimpleAuthenticationInfo(user, info.getCredentials(), getName());
}
}
Since this is a web service project i have a login service:
#POST
#Path("/login")
public Response login(#FormParam("username") final String username, #FormParam("password") final String password, #FormParam("remember") final boolean remember) {
final Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) {
final UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
token.setRememberMe(remember);
currentUser.login(token);
} catch (final AuthenticationException e) {
return Response.status(Status.BAD_REQUEST).entity("Invalid user").build();
}
}
return Response.ok().build();
}
The problem is that SecurityUtils.getSubject().isRemembered() always return false even when i set token.setRememberMe(true);
Is there any configuration that im missing?
Subject.isRemembered() is a little tricky in Shiro. It only returns true if the Subject has a valid Remember Me setting (cookie, etc) AND the Subject is not Authenticated. Details here: http://shiro.apache.org/static/1.2.2/apidocs/org/apache/shiro/subject/Subject.html#isRemembered()
So, I suspect that your Remember Me is working fine, but your expectations for Subject.isRemembered() doesn't match what the method actually does.
Actually if you logout from your application throught shiro logout remember me will be erased. To try make session time out just one minute let session expire itself and reload main page now you will find that user is actually being remembered. LOGOUT CLEARS REMEMBER ME.
If you want to use remember me still after logout you can try to extend securitymanager and use this securitymanger for your application.
public class CustomSecurityManager extends DefaultWebSecurityManager {
#Override
protected void beforeLogout(Subject subject)
{
super.removeRequestIdentity(subject);
}
}

Apache Shiro Authentication with Facebook OAuth

I am stucked in authenticating my application running on Shiro with Facebook OAuth. I really don't know what am I doing wrong. Bascially, my problem is when I get a "code" from Facebook. I want shiro to authenticate it using that code.
This is my authentication code.
FacebookToken token = null;
try{
org.apache.shiro.subject.Subject currentUser = SecurityUtils.getSubject();
//currentUser.logout();
//This is done to avoid temporary multiple url hit.., when the user is not logged out
token = new FacebookToken(code);
currentUser.login(token); //returns true if valid
result = true;
}catch (UnknownAccountException uae) {
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// ... catch more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
log.info("Authentication exception Here.");
}
Here is my facebook token class:
public class FacebookToken implements AuthenticationToken {
private static final long serialVersionUID = 1L;
private String code;
public FacebookToken(){
}
public FacebookToken(String code){
this.code = code;
}
public Object getCredentials() {
return null; //Credentials are handled by facebook
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Object getPrincipal() {
return null; //Not known facebook does the login
}
I have the realm for facebook that is extending authorization realms.
public class FacebookRealm extends AuthorizingRealm {
}
and finally here is my shiro.ini file:
[main]
#authc.loginUrl = /login
#authc.successUrl = /hello
#logout.redirectUrl = /hello
# ------------------------
# Database
# Own Realm
jdbcRealm = com.shiro.common.controller.MyCustomRealm
facebookRealm = com.facebook.login.FacebookRealm
# Sha256
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
# base64 encoding, not hex in this example:
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024
#Facebook Credential matcher
fbCredentialsMatcher = com.facebook.login.FacebookCredentialsMatcher
jdbcRealm.credentialsMatcher = $sha256Matcher
facebookRealm.credentialsMatcher = $fbCredentialsMatcher
# User Query
# default is "select password from users where username = ?"
jdbcRealm.authenticationQuery = SELECT password, salt FROM User WHERE email = ?
# permissions
jdbcRealm.permissionsLookupEnabled = true
jdbcRealm.userRolesQuery = select roleName from UserRole where email = ?
jdbcRealm.permissionsQuery = select permission from RolesPermission where roleName = ?
# Connection
ds = com.mysql.jdbc.jdbc2.optional.MysqlDataSource
ds.serverName = localhost
ds.user = root
ds.password = root123
ds.databaseName = testdb
jdbcRealm.dataSource=$ds
#authc.usernameParam = email
#authc.passwordParam = password
#authc.failureKeyAttribute = shiroLoginFailure
# Use Built-in Chache Manager
builtInCacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager = $builtInCacheManager
#securityManager.realms = $facebookRealm,$jdbcRealm
securityManager.realms = $facebookRealm
# -----------------------------------------------------------------------------
[urls]
#/hello = authc
#/login = authc
#/admin.jsp = authc, perms["admin:access"]
Now when do i debug and reach at currentuser.login methods and go inside, it throws an exception saying
Realm [FacebookRealm#52039826] does not support authentication token [FacebookToken#132d9844]. Please ensure that the appropriate Realm implementation is configured correctly or that the realm accepts AuthenticationTokens of this type.
Please suggest me whether am I doing correct, or not !! Am i missing any configuration or any thing else. Thank you !!
You should extend your FacebookRealm with the following method:
#Override
public boolean supports(AuthenticationToken token) {
return token instanceof FacebookToken;
}
or add the following line to your ini:
facebookRealm.authenticationTokenClass=<realpackage>.FacebookToken

Securing a GWT app with a request param to be checked in a crosscontext attribute

My application is supposed to received a request parameter called sessionId which is supposed to be used to lookup for a crosscontext attribute.
I was looking at Spring Security to implement this and I think already have a good implementation of my AuthenticationProvider :
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
ServletContext servletContext = request.getSession().getServletContext();
String sessionId = request.getParameter("sessionId");
if (sessionId != null) {
ServletContext sc = request.getSession().getServletContext();
Object obj = sc.getContext("/crosscontext").getAttribute(sessionId);
if (obj != null) {
// return new Authentication
}
} else {
logger.error("No session id provided in the request");
return null;
}
if (!GWT.isProdMode()) {
// return new Authentication
} else {
logger.error("No session id provided in the request");
return null;
}
}
Now, what I would like to do is to configure Spring Security to not prompt for a user name and password, to let it reach this authentication provider call the authenticate method.
How can I achieve this ?
I fixed my issue by reviewing the design of my security and going for something closer to the preauthenticated mechanisms that are already provided by Spring Security.
I extended 2 components of Spring Security.
First one is an AbstractPreAuthenticatedProcessingFilter, usually his role is to provide the principal provided in the headers. In my case, I retrieve the header value and search in the context shared between 2 application for an attribute that corresponds to that header and returns it as principal :
public class MyApplicationPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private static final Logger logger = Logger.getLogger(MyApplicationPreAuthenticatedProcessingFilter.class);
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
if (MyApplicationServerUtil.isProdMode()) {
String principal = request.getHeader("MY_HEADER");
String attribute = (String) request.getSession().getServletContext().getContext("/crosscontext").getAttribute(principal);
logger.info("In PROD mode - Found value in crosscontext: " + attribute);
return attribute;
} else {
logger.debug("In DEV mode - passing through ...");
return "";
}
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return null;
}
}
The other component is the AuthenticationProvider which will just check if the authentication contains a principal when it runs in prod mode (GWT prod) :
public class MyApplicationAuthenticationProvider implements AuthenticationProvider {
private static final Logger logger = Logger.getLogger(MyApplicationAuthenticationProvider.class);
public static final String SESSION_ID = "sessionId";
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (MyApplicationServerUtil.isProdMode()) {
if (StringUtils.isNotEmpty((String) authentication.getPrincipal())) {
logger.warn("Found credentials: " + (String) authentication.getPrincipal());
Authentication customAuth = new CustomAuthentication("ROLE_USER");
customAuth.setAuthenticated(true);
return customAuth;
} else {
throw new PreAuthenticatedCredentialsNotFoundException("Nothing returned from crosscontext for sessionId attribute ["
+ (String) authentication.getPrincipal() + "]");
}
} else {
Authentication customAuth = new CustomAuthentication("ROLE_USER");
customAuth.setAuthenticated(true);
return customAuth;
}
}
#Override
public boolean supports(Class<?> authentication) {
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
}
}
I understand that it might not be the most secure application. However, it will already be running in a secure environment. But if you have suggestions for improvement, they're welcome !