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);
}
}
Related
I am looking some way to make some authentication for my play framework app: I want allow/disallow the whole access to non authenticated users
Is there exists some working module/solution for it? I don't need any forms for auth, just 401 HTTP response for non authenticated users (like Apache .htacccess "AuthType Basic" mode).
I've updated Jonck van der Kogel's answer to be more strict in parsing the authorization header, to not fail with ugly exceptions if the auth header is invalid, to allow passwords with ':', and to work with Play 2.6:
So, BasicAuthAction class:
import java.io.UnsupportedEncodingException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import org.apache.commons.codec.binary.Base64;
import play.Logger;
import play.Logger.ALogger;
import play.mvc.Action;
import play.mvc.Http;
import play.mvc.Http.Context;
import play.mvc.Result;
public class BasicAuthAction extends Action<Result> {
private static ALogger log = Logger.of(BasicAuthAction.class);
private static final String AUTHORIZATION = "Authorization";
private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
private static final String REALM = "Basic realm=\"Realm\"";
#Override
public CompletionStage<Result> call(Context context) {
String authHeader = context.request().getHeader(AUTHORIZATION);
if (authHeader == null) {
context.response().setHeader(WWW_AUTHENTICATE, REALM);
return CompletableFuture.completedFuture(status(Http.Status.UNAUTHORIZED, "Needs authorization"));
}
String[] credentials;
try {
credentials = parseAuthHeader(authHeader);
} catch (Exception e) {
log.warn("Cannot parse basic auth info", e);
return CompletableFuture.completedFuture(status(Http.Status.FORBIDDEN, "Invalid auth header"));
}
String username = credentials[0];
String password = credentials[1];
boolean loginCorrect = checkLogin(username, password);
if (!loginCorrect) {
log.warn("Incorrect basic auth login, username=" + username);
return CompletableFuture.completedFuture(status(Http.Status.FORBIDDEN, "Forbidden"));
} else {
context.request().setUsername(username);
log.info("Successful basic auth login, username=" + username);
return delegate.call(context);
}
}
private String[] parseAuthHeader(String authHeader) throws UnsupportedEncodingException {
if (!authHeader.startsWith("Basic ")) {
throw new IllegalArgumentException("Invalid Authorization header");
}
String[] credString;
String auth = authHeader.substring(6);
byte[] decodedAuth = new Base64().decode(auth);
credString = new String(decodedAuth, "UTF-8").split(":", 2);
if (credString.length != 2) {
throw new IllegalArgumentException("Invalid Authorization header");
}
return credString;
}
private boolean checkLogin(String username, String password) {
/// change this
return username.equals("vlad");
}
}
And then, in controller classes:
#With(BasicAuthAction.class)
public Result authPage() {
String username = request().username();
return Result.ok("Successful login as user: " + username + "! Here's your data: ...");
}
You can try this filter:
https://github.com/Kaliber/play-basic-authentication-filter
It looks pretty simple to use and configure.
You could also solve this with a play.mvc.Action, like this.
First your Action:
import org.apache.commons.codec.binary.Base64;
import play.libs.F;
import play.libs.F.Promise;
import play.mvc.Action;
import play.mvc.Http.Context;
import play.mvc.Result;
import util.ADUtil;
public class BasicAuthAction extends Action<Result> {
private static final String AUTHORIZATION = "authorization";
private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
private static final String REALM = "Basic realm=\"yourRealm\"";
#Override
public Promise<Result> call(Context context) throws Throwable {
String authHeader = context.request().getHeader(AUTHORIZATION);
if (authHeader == null) {
context.response().setHeader(WWW_AUTHENTICATE, REALM);
return F.Promise.promise(new F.Function0<Result>() {
#Override
public Result apply() throws Throwable {
return unauthorized("Not authorised to perform action");
}
});
}
String auth = authHeader.substring(6);
byte[] decodedAuth = new Base64().decode(auth);
String[] credString = new String(decodedAuth, "UTF-8").split(":");
String username = credString[0];
String password = credString[1];
// here I authenticate against AD, replace by your own authentication mechanism
boolean loginCorrect = ADUtil.loginCorrect(username, password);
if (!loginCorrect) {
return F.Promise.promise(new F.Function0<Result>() {
#Override
public Result apply() throws Throwable {
return unauthorized("Not authorised to perform action");
}
});
} else {
return delegate.call(context);
}
}
}
Next your annotation:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import play.mvc.With;
#With(BasicAuthAction.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
#Inherited
#Documented
public #interface BasicAuth {
}
You can now annotate your controller functions as follows:
#BasicAuth
public Promise<Result> yourControllerFunction() {
...
I'm afraid there's no such solution, reason is simple: usually when devs need to add authorization/authentication stack they build full solution.
The easiest and fastest way is using HTTP front-end server as a reverse-proxy for your application (I'd choose nginx for that task, but if you have running Apache on the machine it can be used as well). It will allow you to filter/authenticate the traffic with common server's rules
Additionally it gives you other benefits, i.e.: you can create CDN-like path, so you won't waste your apps' resources for serving public, static assets. You can use load-balancer for redeploying your app without stopping it totally for x minutes, etc.
I'm using the awesome ServiceStack to implement my REST backend which serves two iPhone apps written in Xamarin. Everything works great but i'm struggling in getting sessions to work correctly when the two apps are installed on the same device !
The issue is that if I login in one of the apps the second app gets authenticated and doesn't require me to login as a result of 'isCurrentUserAuthenticated()' method below.
I pass cookies with my requests to mimic the browser and to make sure user doesn't have to pass his credentials every time but I guess the problem is that maybe ServiceStack sees two authentication requests from the same IP so it authenticated them both using the first authentication requests succeeds.
Note : The two apps accesses the same database and UserAuth table but every app supports a user role different than the other.
The only way to fix it is to logout from the second app so the user can login again with his credentials to make everything work.
Can you please help with this ?
Here is the code so far :
public static class BLL
{
public static JsonServiceClient ServiceClient { get; set; }
public static string HostUri = "http://test.elasticbeanstalk.com";
public static string HostDomain = "test.elasticbeanstalk.com";
static BLL ()
{
string ss_id = ConfigRepository.GetConfigString ("ss-id");
string ss_pid = ConfigRepository.GetConfigString ("ss-pid");
ServiceClient = new JsonServiceClient (HostUri);
ServiceClient.CookieContainer.Add (new Cookie ("ss-id", ss_id, "/", HostDomain));
ServiceClient.CookieContainer.Add (new Cookie ("ss-pid", ss_pid, "/", HostDomain));
}
public static async Task<bool> isCurrentUserAuthenticated ()
{
bool result = false;
try {
Authenticate authRequest = new Authenticate ();
// Restore the cookie
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
NSUserDefaults.StandardUserDefaults.Synchronize ();
result = true;
} catch (Exception Ex) {
result = false;
}
return result;
}
public static async Task<AuthenticateResponse> Login (string userName, string password)
{
Authenticate authRequest = new Authenticate () {
provider = "credentials",
UserName = userName,
Password = password,
RememberMe = true,
};
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
var cookies = ServiceClient.CookieContainer.GetCookies (new Uri (HostUri));
if (cookies != null) {
var ss_id = cookies ["ss-id"].Value;
var ss_pid = cookies ["ss-pid"].Value;
if (!ss_id.IsNullOrEmpty ()) {
int r = ConfigRepository.AddConfigKey ("ss-id", ss_id);
System.Diagnostics.Debug.WriteLine ("ss-id " + ss_id.ToString ());
}
if (!ss_pid.IsNullOrEmpty ()) {
int r = ConfigRepository.AddConfigKey ("ss-pid", ss_pid);
System.Diagnostics.Debug.WriteLine ("ss-pid " + ss_pid.ToString ());
}
}
NSUserDefaults.StandardUserDefaults.SetString (response.UserId, "UserId");
NSUserDefaults.StandardUserDefaults.Synchronize ();
return response;
}
public static async Task<AuthenticateResponse> Logout ()
{
Authenticate authRequest = new Authenticate () {
provider = "logout"
};
var response = await ServiceClient.PostAsync<AuthenticateResponse> (authRequest);
return response;
}
}
The issue is because you're using the same Session Cookies with a shared ServiceClient instance which ends up referencing the same Authenticated Users Session.
ServiceStack Sessions are only based on the session identifiers (ss-id/ss-pid) specified by the clients cookies, if you use the same cookies you will be referencing the same Authenticated Users Session, they're not affected by IP Address or anything else.
If you want to authenticate as another user, use a new instance of the ServiceClient (so it's not using an existing Sessions Cookies).
Im using apache shiro. When i want to know if the user have permissions and roles i use SecutiryUtils.getSubject(). I like to know how to add more information to the subject like email, primary key and any other business information that i need so i can retrieve that information when necessary.
This is my 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 = com.mycompany.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
[urls]
/logout = logout
/** = authcBasic
This is my JdbcRealm
public class JdbcRealmImpl extends JdbcRealm {
public JdbcRealmImpl() {
super();
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(
final AuthenticationToken token) throws AuthenticationException {
final AuthenticationInfo info = super.doGetAuthenticationInfo(token);
// create a user to test
final User user = new User();
user.setId(11111);
return new SimpleAuthenticationInfo(user, info.getCredentials(),
getName());
}
}
And here is the code where i try to retrieve the user info.
final Subject currentUser = SecurityUtils.getSubject();
final User user = (User) currentUser.getPrincipal();
// null
System.out.println(user);
You should just put that in a database and retrieve it using the Subjects username (for example an emailaddress).
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
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 !