I'm currently using http://www.dropwizard.io/1.1.0/docs/manual/auth.html# Dropwizard-Authentication in my Application. But i like to do the authentication "manualy" which means from a specific login/logout API call on a not authenticated REST interface.
Is there any possibility to forward an REST Call to the Authentication?
#POST
#Path("login")
#Consumes(MediaType.APPLICATION_JSON)
#Timed
#UnitOfWork
public Optional<LoginResponse> login(LoginRequest request) {
// TODO forward login request to authentication
return null;
}
Thx in advance
Thx for helping me. I found a solution like that:
Adding an Authenticator to the REST Client
client = ClientBuilder.newClient();
authenticator = new Authenticator();
client.register(authenticator);
Setup the Authenticator on Login-Successfull
final UserAPIResponse response = create(request, UserAPI.PATH_ATTRIBUTE_DEFINITION_LOGIN);
if (response == null || response.isFailed()) {
connector.setupAuthenticator(null, null);
} else {
connector.setupAuthenticator(request.getUsername(), request.getPassword());
}
And here is the Authenticator
class Authenticator implements ClientRequestFilter {
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
final MultivaluedMap<String, Object> headers = requestContext.getHeaders();
final String basicAuthentication = getBasicAuthentication();
if (basicAuthentication == null) return;
headers.add("Authorization", basicAuthentication);
}
void setup(String username, String password) {
this.user = username;
this.password = password;
}
private String getBasicAuthentication() {
if (user == null || password == null) return null;
final String token = this.user + ":" + this.password;
try {
return "BASIC " + DatatypeConverter.printBase64Binary(token.getBytes("UTF-8"));
} catch (final UnsupportedEncodingException ex) {
throw new IllegalStateException("Cannot encode with UTF-8", ex);
}
}
private String password;
private String user;
}
on the server side i have an Authenticator
public class UserAuthenticator implements Authenticator<BasicCredentials, User> {
UserAuthenticator(UserDAO userDAO) {
this.userDAO = userDAO;
}
#UnitOfWork
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
final String username = credentials.getUsername();
final Optional<DbUser> result = userDAO.getByName(username);
if (!result.isPresent()) return Optional.empty();
final DbUser user = result.get();
final String password = credentials.getPassword();
if (!StringUtils.equals(password, user.getPassword())) return Optional.empty();
if (!user.isOnline()) return Optional.empty();
user.handleAction();
userDAO.save(user);
return Optional.of(UserMgr.convert(user));
}
private final UserDAO userDAO;
}
And to get em working correctly:
SessionDao dao = new SessionDao(hibernateBundle.getSessionFactory());
ExampleAuthenticator exampleAuthenticator = new UnitOfWorkAwareProxyFactory(hibernateBundle)
.create(ExampleAuthenticator.class, SessionDao.class, dao);
So finally there is one REST-Call to login the User and the authentication is done on the result by the client automatically.
Related
Any idea how I can add a header for my calls using SoapCore?
what I have so far:
at startup.cs:
app.UseSoapEndpoint<IMyService>("/MyService.svc", new BasicHttpBinding(), SoapSerializer.DataContractSerializer);
in IMyService
[ServiceContract]
public interface IMyService
{
[OperationContract]
public List<SOADataGetService> GetService(string ServiceType, string ServiceName, string ServiceVersion);
}
then my soap ends up like that:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:GetService>
<tem:ServiceType>?</tem:ServiceType>
<tem:ServiceName>?</tem:ServiceName>
<tem:ServiceVersion>?</tem:ServiceVersion>
</tem:GetService>
</soapenv:Body>
</soapenv:Envelope>
I need to get in <soapenv:Header/> like user and password
You can access the header in SoapCore by implementing and registering a custom IServiceOperationTuner as described in the docs.
e.g.
public class MyServiceOperationTuner : IServiceOperationTuner
{
public void Tune(HttpContext httpContext, object serviceInstance, SoapCore.ServiceModel.OperationDescription operation)
{
if (operation.Name.Equals(nameof(MyService.SomeOperationName)))
{
MyService service = serviceInstance as MyService;
service.SetHttpRequest(httpContext.Request);
}
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IMyService, MyService>();
services.TryAddSingleton<IServiceOperationTuner>(provider => new MyServiceOperationTuner());
}
}
public class MyService : IMyService
{
private ThreadLocal<HttpRequest> _httpRequest = new ThreadLocal<HttpRequest>() { Value = null };
public void SetHttpRequest(HttpRequest request)
{
_httpRequest.Value = request;
}
public string SomeOperationName()
{
var soapHeader = GetHeaderFromRequest(_httpRequest.Value)
return $"SOAP Header: {soapHeader}";
}
private XmlNode GetHeaderFromRequest(HttpRequest request)
{
var bytes = (request.Body as MemoryStream)?.ToArray();
if (bytes == null)
{
// Body missing from request
return null;
}
var envelope = new XmlDocument();
envelope.LoadXml(Encoding.UTF8.GetString(bytes));
return envelope.DocumentElement?.ChildNodes.Cast<XmlNode>().FirstOrDefault(n => n.LocalName == "Header");
}
}
I hope this helps someone. I'm using SoapCore 1.1.0.28 with .Net Core 6. I tried the Tune method listed by #wolfyuk, but Core always returned bytes as null, so I was never able to get past the null check.
The most straightforward way I found is to use IMessageInspector2 from SoapCore to create middleware to intercept the SOAP request on the way in and intercept the SOAP response on the way out. Your class that implements IMessageInspector2 has access to the message so you can extract headers on the way in (that's what I needed), and add headers on the way out. I needed the request headers to be included in my response (a requirement of the system I'm communicating with).
public class AuthMessageFilter : IMessageInspector2
{
private const string WsNamespaceSecurityUri = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
private const string WsUserNameTokenNodeName = "UsernameToken";
private const string WsSecurityNodeName = "Security";
private const string WsTimestampNodeName = "Timestamp";
private readonly IMyService _service;
private readonly IHttpContextAccessor _acc;
private readonly ILogger _logger;
private MessageHeaders _messageHeaders;
public AuthMessageFilter(IHttpContextAccessor acc, IMyService service, ILogger logger)
{
_acc = acc;
_service = service;
_logger = logger;
}
public object AfterReceiveRequest(ref Message message, ServiceDescription serviceDescription)
{
ValidateSoapAction();
var token = GetUserNameToken(message);
var userIsAuthenticated = _service.ValidateUser(token.Username, token.Password.Value).GetAwaiter().GetResult();
if (userIsAuthenticated)
{
_messageHeaders = message.Headers; // use in response.
return null;
}
const string msg = "The user credentials did not authenticate.";
_logger.LogEntry(msg);
throw new AuthenticationFailedException(msg);
}
private void ValidateSoapAction()
{
try
{
var soapAction = _acc.HttpContext?.Request.Headers["SOAPAction"].FirstOrDefault()?.Replace("\"", "");
if (soapAction == null)
{
throw new Exception(
"Error: Could not extract SoapAction from HttpContext.Request.Headers. Aborting SOAP operation.");
}
}
catch (Exception ex)
{
_logger.LogEntry("No SOAP Action found.", ex);
}
}
private WsUsernameToken GetUserNameToken(Message message)
{
WsUsernameToken wsUsernameToken = null;
for (var i = 0; i < _messageHeaders.Count; i++)
{
if (!_messageHeaders[i].Name.Equals(WsSecurityNodeName, StringComparison.OrdinalIgnoreCase))
continue;
using var reader = _messageHeaders.GetReaderAtHeader(i);
while (reader.Read())
{
if (reader.IsStartElement() &&
reader.NamespaceURI.Equals(WsNamespaceSecurityUri, StringComparison.OrdinalIgnoreCase) &&
reader.LocalName.Equals(WsUserNameTokenNodeName, StringComparison.OrdinalIgnoreCase))
{
var serializer = new XmlSerializer(typeof(WsUsernameToken));
wsUsernameToken = (WsUsernameToken)serializer.Deserialize(reader);
break;
}
}
break;
}
if (wsUsernameToken == null)
{
var ex = new SecurityException("An exception occurred when verifying security for the message.");
_logger.LogEntry(LoggingCategory.Service, LoggingLevel.Error, ex.Message, ex);
throw ex;
}
return wsUsernameToken;
}
public void BeforeSendReply(ref Message reply, ServiceDescription serviceDescription, object correlationState)
{
for (var i = 0; i < _messageHeaders.Count; i++)
{
if (!_messageHeaders[i].Name.Equals(WsSecurityNodeName, StringComparison.OrdinalIgnoreCase))
continue;
using var reader = _messageHeaders.GetReaderAtHeader(i);
while (reader.Read())
{
if (reader.IsStartElement() &&
reader.NamespaceURI.Equals(WsNamespaceSecurityUri, StringComparison.OrdinalIgnoreCase) &&
reader.LocalName.Equals(WsTimestampNodeName, StringComparison.OrdinalIgnoreCase))
{
reply.Headers.Add(_messageHeaders[i] as MessageHeader);
break;
}
}
break;
}
}
}
Do not use SoapCore it is outdated, try to use SmartSoap:
https://github.com/Raffa50/SmartSoap
it is also available as a nugetPackage:
https://www.nuget.org/packages/Aldrigos.SmartSoap.AspNet/
Have a look at it, try it and if you need further support I will be pleased to help you!
My project is based on Grail 2.5.6 and Spring plugins. I'm trying to create a custom auth provider, filter and token extending their respective basic classes.
this.getAuthenticationManager().authenticate(authRequest)
In my filter the authentication manager is always null. So, it throws cannot invoke authenticate() on a null object. When I debug on the authenticationManager, it lists other provider names but my custom one.
Here is my custom web security config
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true)
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
OrbisAuthenticationProvider orbisAuthenticationProvider
public CustomWebSecurityConfig() {
super()
log.debug "configure custom security"
print("configure custom security")
}
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
print("configure method 1")
log.debug "configure method 1"
auth.authenticationProvider(orbisAuthenticationProvider)
}
#Bean(name= BeanIds.AUTHENTICATION_MANAGER)
#Override
AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean()
}
#Bean
OrbisAuthenticationFilter orbisAuthenticationProvider() throws Exception {
log.debug "orbis Authentication provider"
OrbisAuthenticationProvider orbisAuthenticationProvider = new OrbisAuthenticationProvider(authenticationManagerBean())
return orbisAuthenticationProvider
}
#Bean
#Autowired
public OrbisAuthenticationFilter orbisAuthenticationFilter() throws Exception {
print("configure orbis filtr")
OrbisAuthenticationFilter oaf = new OrbisAuthenticationFilter()
oaf.setAuthenticationManager(authenticationManagerBean())
oaf.setFilterProcessesUrl("j_orbis_security_check")
oaf.setUsernameParameter("email")
oaf.setPasswordParameter("password")
oaf.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler()
.setDefaultTargetUrl("/oauth/authorize"))
oaf.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler()
.setDefaultFailureUrl("/loginWithOrbis"))
oaf.afterPropertiesSet()
return oaf
}
}
On debugging, it doesn't look like any of these methods are getting called. The annotations don't seem enough to get picked up. I had tried #ComponentScan too.
Do I have to inject this security config somewhere? How do I get authManager to be available in my filter?
OrbisAuthFilter
class OrbisAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// #Autowired
OrbisAuthenticationProvider orbisAuthenticationProvider
OrbisAuthenticationFilter() {
super("/j_orbis_security_check")
orbisAuthenticationProvider = new OrbisAuthenticationProvider()
}
void afterPropertiesSet() {
assert authenticationManager != null, 'authenticationManager must be specified'
}
#Override
Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("email")
String password = request.getParameter("password")
String accessCode = request.getParameter("accessCode")
OrbisAuthenticationToken authRequestForAuthentication = new OrbisAuthenticationToken(username, password, accessCode)
// This throws error because getAuthenticationManager returns null
// authRequestForAuthentication = this.getAuthenticationManager.authenticate(authRequestForAuthentication)
//This works if I instantiate the orbis provider object in the constructor
authRequestForAuthentication = this.orbisAuthenticationProvider.authenticate(authRequestForAuthentication)
SecurityContextHolder.getContext().setAuthentication(authRequestForAuthentication)
return authRequestForAuthentication
}
protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
#Override
#Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authenticationManager);
}
}
OrbisAuthProvider
class OrbisAuthenticationProvider implements AuthenticationProvider {
#Override
Authentication authenticate(Authentication authentication) throws AuthenticationException {
OrbisAuthenticationToken orbisAuth = (OrbisAuthenticationToken) authentication
String username = orbisAuth.principal
String password = orbisAuth.credentials
String orbisAccessCode = orbisAuth.orbisAccessCode
def urlToUse = 'https://coopstatus.neu.edu/sail_api/full.aspx?' + 'ac=' + orbisAccessCode + '&e='+ username + '&p=' + password
HttpClient httpClient = DefaultHttpClient.newInstance()
HttpGet getRequest = new HttpGet(urlToUse)
HttpResponse httpResponse = httpClient.execute(getRequest)
JSONObject orbisResponse = new JSONObject(httpResponse.getEntity().getContent().getText())
// if(orbisResponse.get("IsFound")) {
// //Return error not authenticated
// }
Collection<GrantedAuthority> orbisUserGrantedAuthorities = getLDAPUserAuthorities(orbisResponse.get("Email"))
orbisAuth = new OrbisAuthenticationToken(username, password, orbisAccessCode, orbisUserGrantedAuthorities)
return orbisAuth
}
private Collection<GrantedAuthority> getLDAPUserAuthorities(String username) {
LDAPUserDetails currentLdapUserDetails
try {
currentLdapUserDetails = new LDAPUserDetailsService().loadUserByOrbisUsername(username)
log.debug currentLdapUserDetails
} catch(org.springframework.security.core.userdetails.UsernameNotFoundException e) {
log.error("User " + username + " not found in ldap", e)
}
Collection<GrantedAuthority> authorities = new ArrayList<>()
for (String authority : currentLdapUserDetails.authorities) {
authorities.add(new SimpleGrantedAuthority(authority))
}
return authorities
}
#Override
public boolean supports(Class<?> authentication) {
return (OrbisAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
Resources.groovy
import edu.neu.security.OrbisAuthenticationFilter
import edu.neu.security.OrbisAuthenticationProvider
beans = {
userDetailsService(edu.neu.security.LDAPUserDetailsService)
orbisAuthenticationProvider(OrbisAuthenticationProvider)
orbisAuthenticationFilter(OrbisAuthenticationFilter) {
orbisAuthenticationProvider = ref("orbisAuthenticationProvider")
requiresAuthenticationRequestMatcher = ref('filterProcessUrlRequestMatcher')
// This throws error during startup. Unable to init bean
// authenicationManager = ref("authenicationManager")
}
myOAuth2ProviderFilter(OAuth2ProviderFilters) {
//grailsApplication = ref('grailsApplication')
// properties
}
}
I followed some of the concepts from this project: https://github.com/ppazos/cabolabs-ehrserver/
Even if the whole process is executed and securityContext is set with authenticated, when I hit oauth/authorize to get Authorization_Code, it redirects back to '/login/auth'. It still doesn't know that a user is already authenticated.
When you add an authentication provider to the AuthenticationManagerBuilder bean (which comes from AuthenticationConfiguration), the authentication manager bean you declare is not used.
Try:
#Configuration
#EnableGlobalMethodSecurity(securedEnabled=true)
public class CustomWebSecurityConfig {
OrbisAuthenticationProvider lwoAuthProvider;
public CustomWebSecurityConfig() {
//
}
#Bean(name= BeanIds.AUTHENTICATION_MANAGER)
AuthenticationManager authenticationManagerBean() throws Exception {
return new ProviderManager(Arrays.asList(lwoAuthProvider));
}
Your AuthenticationManager bean should get picked up and will be used for method security. You can also #Autowire it in your filter if it is being managed by Spring, or #Autowire it in the #Configuration class that instantiates your filter.
NOTE: the above class WILL NOT create any of the Spring Security filters.
(The filter chain wasn't being created anyway - you didn't annotate your class with #EnableWebSecurity)
we use jhipster to generate gateway and microservices with OAuth2 authentication, and that works fine with a JHipster Registry and a Keycloak server. But we have a microservice that will be called from an external service, and this service use basic authentication.
So, on the gateway, we need to send login and pasword from basic auth to keycloak server, and use access token to call our service. I get access token by adding filter in MicroserviceSecurityConfiguration class :
http.addFilterBefore(basicAuthFilter, UsernamePasswordAuthenticationFilter.class);
And here a extract of filter method :
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setAccessTokenUri("http://keycloakserver/auth/realms/jhipster/protocol/openid-connect/token");
details.setGrantType("password");
details.setClientId("clientId");
details.setClientAuthenticationScheme(AuthenticationScheme.form);
details.setUsername(login);
details.setPassword(password);
AccessTokenRequest tokenRequest = new DefaultAccessTokenRequest();
ResourceOwnerPasswordAccessTokenProvider provider = new ResourceOwnerPasswordAccessTokenProvider();
OAuth2AccessToken accessToken = provider.obtainAccessToken(details, tokenRequest);
I guess I have to store this token in tokenStore, but I don't know how. So my questions are how use this token, and does the the way I get it is correct ?
Thanks for your help !
I Have same problem with you in my production system, then I changed Authentication server to JHipster UAA server, and problem is resolved.
I think you are using first kind now:
OAuth 2.0 / OIDC Authentication:
this uses an OpenID Connect server, like Keycloak or Okta, which handles authentication outside of the application.
Authentication with JHipster UAA server:
this uses a JHipster UAA server that must be generated separately, and which is an OAuth2 server that handles authentication outside of the application.
After some tests and trial and error, I managed to do that I want.
First, I created a BasicAuthenticationFilter class :
#Configuration
public class BasicAuthenticationFilter implements Filter {
private static final String GRANT_TYPE = "password";
private static final String BASIC_AUTH_HEADER = "Authorization";
private static final String BASIC_PREFIX = "Basic ";
#Value("${security.oauth2.client.access-token-uri}")
private String accessTokenUri;
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Autowired
private TokenStore tokenStore;
private ResourceOwnerPasswordAccessTokenProvider provider;
public BasicAuthenticationFilter( ) {
provider = new ResourceOwnerPasswordAccessTokenProvider();
}
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = null;
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
header = httpRequest.getHeader(BASIC_AUTH_HEADER);
if (header != null && header.startsWith(BASIC_PREFIX)) {
String base64 = header.substring(BASIC_PREFIX.length());
String loginPassword = new String(Base64.getDecoder().decode(base64.getBytes()));
String[] split = loginPassword.split(":");
String login = split[0];
String password = split[1];
authenticate(httpRequest, login, password);
}
}
chain.doFilter(request, response);
}
private void authenticate(HttpServletRequest httpRequest, String login, String password) {
ResourceOwnerPasswordResourceDetails details = new ResourceOwnerPasswordResourceDetails();
details.setAccessTokenUri(accessTokenUri);
details.setGrantType(GRANT_TYPE);
details.setClientId(clientId);
details.setClientAuthenticationScheme(AuthenticationScheme.query);
details.setUsername(login);
details.setPassword(password);
DefaultAccessTokenRequest tokenRequest = new DefaultAccessTokenRequest();
tokenRequest.setCurrentUri(httpRequest.getRequestURI());
try {
OAuth2AccessToken accessToken = provider.obtainAccessToken(details, tokenRequest);
OAuth2Authentication oauth2Authentication = tokenStore.readAuthentication(accessToken);
AccessTokenDetails accessTokenDetail = new AccessTokenDetails(accessToken.getValue());
oauth2Authentication.setDetails(accessTokenDetail);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(oauth2Authentication);
} catch (OAuth2AccessDeniedException e) {
throw new AccessDeniedException("Wrong credentials !");
}
}
#Override
public void destroy() {
}
public static class AccessTokenDetails {
private static final String DEFAULT_TOKEN_TYPE = "bearer";
public final String tokenType;
public final String tokenValue;
public AccessTokenDetails(String tokenValue) {
this(DEFAULT_TOKEN_TYPE, tokenValue);
}
public AccessTokenDetails(String tokenType, String tokenValue) {
this.tokenType = tokenType;
this.tokenValue = tokenValue;
}
}
}
This filter check if there are basic authentication, and if yes, authenticate user. The authentication details are stored in internal class AccessTokenDetails. So, the token can be read in AuthorizationHeaderUtil :
public class AuthorizationHeaderUtil {
public static String getAuthorizationHeader() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
Object details = authentication.getDetails();
String tokenType = "";
String tokenValue = "";
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oauth2Details = (OAuth2AuthenticationDetails) details;
tokenType = oauth2Details.getTokenType();
tokenValue = oauth2Details.getTokenValue();
} else if (details instanceof AccessTokenDetails) {
AccessTokenDetails accessTokenDetails = (AccessTokenDetails) details;
tokenType = accessTokenDetails.tokenType;
tokenValue = accessTokenDetails.tokenValue;
}
return String.format("%s %s", tokenType, tokenValue);
}
}
This class has been generated by JHipster, I add a check for the two authentication detail class I use.
I hope that will be useful.
Denis
I am implementing a LoginPage with Wicket, and I am not getting it, how to write the custom Feedback messages, for ex, "Password is wrong", "Username is wrong" or "Accound is locked out" (the last example should be a bit more difficult because it is related to Ldap/Ldap error messages I think.. But I think there is an easier way for the second two, with the properties file of my LoginPage or something like that.. I tried to change the default Wicket "login failed" message, and this through the properties' file of my page, I just added "signFailed=Sign in failed TEST", and it got changed.. but didn't got it how to tell the user why! Pass or username is wrong!
here the implementation:
public class LoginPage extends SampleManagementPage {
private static final long serialVersionUID = -8585718500226951823L;
private SignInPanel signIn;
public LoginPage() {
signIn = new SignInPanel("signInPanel");
add(signIn);
}
}
and my SampleManagementPage extends WebPage!
here the properties' file of LoginPage:
page.title=Login for Sample Management
signInFailed=Sign in failed TEST
The reason why you are able to change only the signFailed error message, is that wicket SignInPanel throws only this particular error in case it's sign-in form fails to authenticate. To see that, you can open the source code of SignInPanel.java.
One way to overcome the problem and produce your own error messages, is write your own sign-in panel. I m not saying it is the only way but it worked for me :)
public class LoginPanel extends Panel {
private static final long serialVersionUID = -1662154893824849377L;
private static final String SIGN_IN_FORM = "signInForm";
private boolean includeRememberMe = true;
private boolean rememberMe = true;
private String password;
private String username;
public LoginPanel(final String id, final boolean includeRememberMe) {
super(id);
this.includeRememberMe = includeRememberMe;
add(new FeedbackPanel("feedback"));
add(new SignInForm(SIGN_IN_FORM));
}
#Override
protected void onConfigure() {
if (isSignedIn() == false) {
IAuthenticationStrategy authenticationStrategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
String[] data = authenticationStrategy.load();
if ((data != null) && (data.length > 1)) {
if (signIn(data[0], data[1])) {
username = data[0];
password = data[1];
onSignInRemembered();
} else {
authenticationStrategy.remove();
}
}
}
super.onConfigure();
}
private boolean signIn(String username, String password) {
return AuthenticatedWebSession.get().signIn(username, password);
}
private boolean userExists(String username) {
return ((MyWebSession) AuthenticatedWebSession.get()).userExists(username);
}
private boolean isSignedIn() {
return AuthenticatedWebSession.get().isSignedIn();
}
protected void onSignInFailed() {
error(getLocalizer().getString("signInFailed", this, "Wrong password"));
}
private void onUserExistsFailed() {
error(getLocalizer().getString("userExistsFailed", this, "User does not exist"));
}
protected void onSignInSucceeded() {
continueToOriginalDestination();
setResponsePage(getApplication().getHomePage());
}
protected void onSignInRemembered() {
continueToOriginalDestination();
throw new RestartResponseException(getApplication().getHomePage());
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public LoginPanel(final String id) {
this(id, true);
}
public final class SignInForm extends StatelessForm<LoginPanel> {
private static final long serialVersionUID = 1L;
public SignInForm(final String id) {
super(id);
setModel(new CompoundPropertyModel<LoginPanel>(LoginPanel.this));
add(new TextField<String>("username"));
add(new PasswordTextField("password"));
WebMarkupContainer rememberMeRow = new WebMarkupContainer("rememberMeRow");
add(rememberMeRow);
rememberMeRow.add(new CheckBox("rememberMe"));
rememberMeRow.setVisible(includeRememberMe);
}
#Override
public final void onSubmit() {
IAuthenticationStrategy strategy = getApplication().getSecuritySettings().getAuthenticationStrategy();
if (!userExists(username)) {
onUserExistsFailed();
strategy.remove();
return;
}
if (signIn(getUsername(), getPassword())) {
if (rememberMe == true) {
strategy.save(username, password);
} else {
strategy.remove();
}
onSignInSucceeded();
} else {
onSignInFailed();
strategy.remove();
}
}
}
}
public class MyWebSession extends AuthenticatedWebSession {
private static final long serialVersionUID = -401924496527311251L;
public MyWebSession(Request request) {
super(request);
}
public boolean userExists(String username) {
// business login
}
#Override
public boolean authenticate(String username, String password) {
// business login
}
#Override
public Roles getRoles() {
Roles roles = new Roles();
roles.add(Roles.USER);
return roles;
}
}
Im trying to make a service can only be accessed if the client is authenticated and I put the Authenticate attribute but it did not work because when I can access the service without being authenticated. I placed the Authenticate attribute before the Request DTO , top of a service and before the Action. Here is some code of the service I want to secure
[Authenticate]
public class HelloService : Service
{
public const string HelloServiceCounterKey = "HelloServiceCounter";
public object Any(HelloRequest request)
{
var userSession = SessionAs<AppHost.CustomUserSession>();
Session.Set(HelloServiceCounterKey, Session.Get<int>(HelloServiceCounterKey) + 1);
var roles = string.Join(", ", userSession.Roles.ToArray());
return new HelloResponse { Result = "Hello, " + request.Name + ", your role(s): " + roles };
}
}
I have this in my AppHost Configure(Funq.Container container)
Plugins.Add(new AuthFeature(
() => new CustomUserSession(),
new[] { new CustomCredentialsAuthProvider() }
));
public class CustomUserSession : AuthUserSession
{
public string CompanyName { get; set; }
}
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
if (!Membership.ValidateUser(userName, password)) return false;
var session = (CustomUserSession)authService.GetSession(false);
session.CompanyName = "Company from DB";
session.UserAuthId = userName;
session.IsAuthenticated = true;
// add roles
session.Roles = new List<string>();
if (session.UserAuthId == "admin") session.Roles.Add(RoleNames.Admin);
session.Roles.Add("User");
return true;
}
}
When access the service in the line of
var roles = string.Join(", ", userSession.Roles.ToArray());
obviosly return NULL because have not been autheticated.
What should do Authenticate attribute on this case?
you need to configure your authentication provider in you app host configuration as follows:
public override void Configure(Container container)
{
Plugins.Add(new AuthFeature(() => new AuthUserSession(), new IAuthProvider[]
{
your providers here...
}));
}
Edit:
Assuming that CustomUserSession inherits from IAuthSession you can change
var session = (CustomUserSession)authService.GetSession(false);
To
var session = authService.GetSession<CustomUserSession>();
and as far as I see you aren't saving the session after the authentication
try something like this:
public override object Authenticate(IServiceBase authService, IAuthSession session, ServiceStack.ServiceInterface.Auth.Auth request)
{
string userName = request.UserName;
string password = request.Password;
if (!LoginMatchesSession(session, userName))
{
authService.RemoveSession();
session = authService.GetSession();
}
if (TryAuthenticate(authService, userName, password))
{
authService.SaveSession(session, SessionExpiry);
if (session.UserAuthName == null)
session.UserAuthName = userName;
OnAuthenticated(authService, session, null, null);
return new AuthResponse
{
UserName = userName,
SessionId = session.Id,
ReferrerUrl = RedirectUrl
};
}
throw new HttpError(HttpStatusCode.BadRequest, "400", "wrong credentials");
}
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
var session = authService.GetSession<CustomUserSession>();
if (!Membership.ValidateUser(userName, password)) return false;
session.IsAuthenticated = true;
session.Id = authService.GetSessionId();
return true;
}
EDIT:
there's other missing part you need to configure a cacheclient since all the session are managed in cache
try something like that:
container.Register<ICacheClient>(new MemoryCacheClient(){FlushOnDispose = false});
can you update your code with your host configuration?