may i know possible to use spring security to limit max number of users able to login to website at the same time?
definately, not concurrent-session-control parameter. what i want is for instance, i want to limit maximum only allow 1000 users login same time. if more than that forward to notice page stating maximum users exceeded
You can use Spring Security's concurrent session control by accessing the SessionRegistry to find out how many users are currently logged in. In Spring Security 3, the ConcurrentSessionControlStrategy is responsible for controlling whether the user is allowed to create a session after logging in. You can extend this class and add an extra check based on the number of users:
public class MySessionAuthenticationStrategy extends ConcurrentSessionControlStrategy {
int MAX_USERS = 1000; // Whatever
SessionRegistry sr;
public MySessionAuthenticationStrategy(SessionRegistry sr) {
super(sr);
this.sr = sr;
}
#Override
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
if (sr.getAllPrincipals().size() > MAX_USERS) {
throw new SessionAuthenticationException("Maximum number of users exceeded");
}
super.onAuthentication(authentication, request, response);
}
}
You would then inject this into the security namespace as described in the Spring Security reference manual.
In Spring Security 2.0, the concurrent session control is implemented slightly differently and you would customize the ConcurrentSessionController instead.
I do not have enough reputation to add a comment. But getAllPrincipals returns all principals including ones from expired sessions. Use some method like below to getAllActiveSessions.
private List<SessionInformation> getActiveSessions(SessionRegistry sessionRegistry) {
final List<Object> principals = sessionRegistry.getAllPrincipals();
if (principals != null) {
List<SessionInformation> sessions = new ArrayList<>();
for (Object principal : principals) {
sessions.addAll(sessionRegistry.getAllSessions(principal, false));
}
return sessions;
}
return Collections.emptyList();
}
this post is a bit old but I have had the same problem in spring security 4.1 and I have solved it like that.
session-management
<security:http disable-url-rewriting="true" use-expressions="true" auto-config="true">
<security:session-management invalid-session-url="/app/login" session-authentication-strategy-ref="sessionAuthenticationStrategy">
</security:session-management>
</security:http>
session-authentication-strategy-ref
<bean id="sessionAuthenticationStrategy" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<constructor-arg>
<list>
<bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<constructor-arg ref="sessionRegistry"/>
<property name="maximumSessions" value="1" />
<property name="exceptionIfMaximumExceeded" value="true" />
</bean>
<bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</bean>
<bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<constructor-arg ref="sessionRegistry"/>
</bean>
</list>
</constructor-arg>
</bean>
SessionRegistry
#Autowired
private SessionRegistry sessionRegistry;
Authentication
List<SessionInformation> sessions = new ArrayList<>();
for (Object principal : sessionRegistry.getAllPrincipals()) {
sessions.addAll(sessionRegistry.getAllSessions(principal, false));
}
LOGGER.info("Sessiones Activas: " + sessions.size());
// filtro para limite de sessiones
if (sessions.size() < max_sessions) {
//authentication
} else {
throw new SessionAuthenticationException("Maximo numero de Usuarios exedido.");
}
in this way because I am authenticating based on security: custom-filter
Related
After registration of a new user, generated hashed password in database don't match password from user input typed in order to authenticate. Raw passwords are the same but hashed versions are different. I wonder how to get these two match each other for proper authentication? I'm using Spring 4.3.2.RELEASE, and 4.2.0.RELEASE for Security.
Also I have a warning:
WARN SpringSecurityCoreVersion:78 - **** You are advised to use Spring 4.3.4.RELEASE or later with this version. You are running: 4.3.0.RELEASE
Maybe this is causing a problem in some way.
config.xml:
<bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" />
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<security:password-encoder ref="encoder"/>
</security:authentication-provider>
</security:authentication-manager>
<bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<property name="userDetailsService" ref="userService" />
<property name="hideUserNotFoundExceptions" value="false" />
<property name="passwordEncoder" ref="encoder" />
</bean>
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<ref bean="daoAuthenticationProvider" />
</constructor-arg>
</bean>
UserEntity.java:
public void setPassword(String password) {
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
this.password = passwordEncoder.encode(password);
}
UserAuthenticationProviderService.java:
public boolean processUserAuthentication(UserEntity user) {
try {
Authentication request = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
return true;
} catch(AuthenticationException e) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(FacesMessage.SEVERITY_ERROR, e.getMessage(), "Catched Error!"));
return false;
}
}
EDIT: Solved.
As Shaun said problem was encoding in Entity class. After moving encoding to place of user creation everything works well, because encoding now appears only in user creation process. Thanks!
Yes, you noted right after 2 encoding of the same string by BCryptEncoder you will get different strings. But Spring Security doesn't use matching for equals. When you register you encoder, SPring Security would use boolean matches(CharSequence rawPassword, String encodedPassword) from PasswordEncoder(BCryptEncoder implements this interface).
If you are interesting about details, you can view implementation of BCrypt, its pretty simple:
static boolean equalsNoEarlyReturn(String a, String b) {
char[] caa = a.toCharArray();
char[] cab = b.toCharArray();
if (caa.length != cab.length) {
return false;
}
byte ret = 0;
for (int i = 0; i < caa.length; i++) {
ret |= caa[i] ^ cab[i];
}
return ret == 0;
}
Is it possible to generate multiple valid access tokens using the client_credentials or password grant type per request?
Generating a token using the above grant types only gives a new token when the current one expires per request.
I can use the password grant type to generate a refresh token and then generate multiple access tokens, but doing that will invalidate any previous access tokens.
Any idea how i could change to allow an access token to be generated per request to the /oauth/token endpoint and insure that any previous tokens are not invalidated?
Below is the XML configuration of my oauth server.
<!-- oauth2 config start-->
<sec:http pattern="/test/oauth/token" create-session="never"
authentication-manager-ref="authenticationManager" >
<sec:intercept-url pattern="/test/oauth/token" access="IS_AUTHENTICATED_FULLY" />
<sec:anonymous enabled="false" />
<sec:http-basic entry-point-ref="clientAuthenticationEntryPoint"/>
<sec:custom-filter ref="clientCredentialsTokenEndpointFilter" before="BASIC_AUTH_FILTER" />
<sec:access-denied-handler ref="oauthAccessDeniedHandler" />
</sec:http>
<bean id="clientCredentialsTokenEndpointFilter"
class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider user-service-ref="clientDetailsUserService" />
</sec:authentication-manager>
<bean id="clientDetailsUserService"
class="org.springframework.security.oauth2.provider.client.ClientDetailsUserDetailsService">
<constructor-arg ref="clientDetails" />
</bean>
<bean id="clientDetails" class="org.security.oauth2.ClientDetailsServiceImpl"></bean>
<bean id="clientAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="springsec/client" />
<property name="typeName" value="Basic" />
</bean>
<bean id="oauthAccessDeniedHandler"
class="org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler"/>
<oauth:authorization-server
client-details-service-ref="clientDetails" token-services-ref="tokenServices">
<oauth:authorization-code />
<oauth:implicit/>
<oauth:refresh-token/>
<oauth:client-credentials />
<oauth:password authentication-manager-ref="userAuthenticationManager"/>
</oauth:authorization-server>
<sec:authentication-manager id="userAuthenticationManager">
<sec:authentication-provider ref="customUserAuthenticationProvider">
</sec:authentication-provider>
</sec:authentication-manager>
<bean id="customUserAuthenticationProvider"
class="org.security.oauth2.CustomUserAuthenticationProvider">
</bean>
<bean id="tokenServices"
class="org.springframework.security.oauth2.provider.token.DefaultTokenServices">
<property name="tokenStore" ref="tokenStore" />
<property name="supportRefreshToken" value="true" />
<property name="accessTokenValiditySeconds" value="300"></property>
<property name="clientDetailsService" ref="clientDetails" />
</bean>
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
<constructor-arg ref="jdbcTemplate" />
</bean>
<bean id="jdbcTemplate"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/oauthdb"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>
<bean id="oauthAuthenticationEntryPoint"
class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
</bean>
Updated on 21/11/2014
When I double check, I found that InMemoryTokenStore use a OAuth2Authentication's hash string as key of serveral Map. And when I use same username, client_id, scope.. and I got same key. So this may leading to some problem. So I think the old way are deprecated. The following is what I did to avoid the problem.
Create another AuthenticationKeyGenerator that can calculate unique key, called UniqueAuthenticationKeyGenerator
/*
* Copyright 2006-2011 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
/**
* Basic key generator taking into account the client id, scope, resource ids and username (principal name) if they
* exist.
*
* #author Dave Syer
* #author thanh
*/
public class UniqueAuthenticationKeyGenerator implements AuthenticationKeyGenerator {
private static final String CLIENT_ID = "client_id";
private static final String SCOPE = "scope";
private static final String USERNAME = "username";
private static final String UUID_KEY = "uuid";
public String extractKey(OAuth2Authentication authentication) {
Map<String, String> values = new LinkedHashMap<String, String>();
OAuth2Request authorizationRequest = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
values.put(USERNAME, authentication.getName());
}
values.put(CLIENT_ID, authorizationRequest.getClientId());
if (authorizationRequest.getScope() != null) {
values.put(SCOPE, OAuth2Utils.formatParameterList(authorizationRequest.getScope()));
}
Map<String, Serializable> extentions = authorizationRequest.getExtensions();
String uuid = null;
if (extentions == null) {
extentions = new HashMap<String, Serializable>(1);
uuid = UUID.randomUUID().toString();
extentions.put(UUID_KEY, uuid);
} else {
uuid = (String) extentions.get(UUID_KEY);
if (uuid == null) {
uuid = UUID.randomUUID().toString();
extentions.put(UUID_KEY, uuid);
}
}
values.put(UUID_KEY, uuid);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).");
}
try {
byte[] bytes = digest.digest(values.toString().getBytes("UTF-8"));
return String.format("%032x", new BigInteger(1, bytes));
}
catch (UnsupportedEncodingException e) {
throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).");
}
}
}
Finally, wire them up
<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.store.JdbcTokenStore">
<constructor-arg ref="jdbcTemplate" />
<property name="authenticationKeyGenerator">
<bean class="your.package.UniqueAuthenticationKeyGenerator" />
</property>
</bean>
Below way may leading to some problem, see updated answer!!!
You are using DefaultTokenServices. Try this code and make sure to re-define your `tokenServices`
package com.thanh.backend.oauth2.core;
import java.util.Date;
import java.util.UUID;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.DefaultExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
/**
* #author thanh
*/
public class SimpleTokenService extends DefaultTokenServices {
private TokenStore tokenStore;
private TokenEnhancer accessTokenEnhancer;
#Override
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
OAuth2RefreshToken refreshToken = createRefreshToken(authentication);;
OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
tokenStore.storeAccessToken(accessToken, authentication);
tokenStore.storeRefreshToken(refreshToken, authentication);
return accessToken;
}
private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
if (validitySeconds > 0) {
token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
}
token.setRefreshToken(refreshToken);
token.setScope(authentication.getOAuth2Request().getScope());
return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
}
private ExpiringOAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
if (!isSupportRefreshToken(authentication.getOAuth2Request())) {
return null;
}
int validitySeconds = getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
ExpiringOAuth2RefreshToken refreshToken = new DefaultExpiringOAuth2RefreshToken(UUID.randomUUID().toString(),
new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
return refreshToken;
}
#Override
public void setTokenEnhancer(TokenEnhancer accessTokenEnhancer) {
super.setTokenEnhancer(accessTokenEnhancer);
this.accessTokenEnhancer = accessTokenEnhancer;
}
#Override
public void setTokenStore(TokenStore tokenStore) {
super.setTokenStore(tokenStore);
this.tokenStore = tokenStore;
}
}
Following #Thanh Nguyen Van approach:
I stumbled upon the same problem while developing my backend with Spring Boot and OAuth2. The problem I encountered was that, if multiple devices shared the same tokens, once one device refreshed the token, the other device would be clueless and, long story short, both devices entered in a token refresh frenzy. My solution was to replace the default AuthenticationKeyGenerator with a custom implementation which overrides DefaultAuthenticationKeyGenerator and adds a new parameter client_instance_id in the key generator mixture. My mobile clients would then send this parameter which has to be unique across app installs (iOS or Android). This is not a special requirement, since most mobile apps already track the application instance in some form.
public class EnhancedAuthenticationKeyGenerator extends DefaultAuthenticationKeyGenerator {
public static final String PARAM_CLIENT_INSTANCE_ID = "client_instance_id";
private static final String KEY_SUPER_KEY = "super_key";
private static final String KEY_CLIENT_INSTANCE_ID = PARAM_CLIENT_INSTANCE_ID;
#Override
public String extractKey(final OAuth2Authentication authentication) {
final String superKey = super.extractKey(authentication);
final OAuth2Request authorizationRequest = authentication.getOAuth2Request();
final Map<String, String> requestParameters = authorizationRequest.getRequestParameters();
final String clientInstanceId = requestParameters != null ? requestParameters.get(PARAM_CLIENT_INSTANCE_ID) : null;
if (clientInstanceId == null || clientInstanceId.length() == 0) {
return superKey;
}
final Map<String, String> values = new LinkedHashMap<>(2);
values.put(KEY_SUPER_KEY, superKey);
values.put(KEY_CLIENT_INSTANCE_ID, clientInstanceId);
return generateKey(values);
}
}
which you would then inject in a similar manner:
final JdbcTokenStore tokenStore = new JdbcTokenStore(mDataSource);
tokenStore.setAuthenticationKeyGenerator(new EnhancedAuthenticationKeyGenerator());
The HTTP request would then look something like this
POST /oauth/token HTTP/1.1
Host: {{host}}
Authorization: Basic {{auth_client_basic}}
Content-Type: application/x-www-form-urlencoded
grant_type=password&username={{username}}&password={{password}}&client_instance_id={{instance_id}}
The benefit of using this approach is that, if the client doesn't send a client_instance_id, the default key would be generated, and if an instance is provided, the same key is returned every time for the same instance. Also, the key is platform independent. The downside would be that the MD5 digest (used internally) is called two times.
Don't set any scope values at backend side keep empty and at the time of generating access token send sessionId or deviceId or any unique ID to scope , then Always you will get new Token For same client and user combination.
When I use MyIbatis with spring transaction, using the Annotation driven declarative method, do I have do bother manage the session.
Without the transaction I usually do like this:
public int insertPnt(Movimenti value) {
try (SqlSession session = sqlSessionFactory.openSession()) {
for (Movimenti value : values) {
session.insert(
"com.sirio.cisl.dal.MovimentiMapper.insertSelective",
value);
}
} catch (Exception e) {
log.error("Error inserting movimenti "+user+" anno "+anno+" "+ e.getMessage());
throw e;
}
}
but from MyIbatis-Spring documentation I read
MyBatis SqlSession provides you with specific methods to handle
transactions programmatically. ...........
. That means that Spring will always handle your
transactions.
You cannot call SqlSession.commit(), SqlSession.rollback() or
SqlSession.close() over a Spring managed SqlSession.
So I wandering if I'm doing correct when I just adding the #Transaction annotation (and the <tx:annotation-driven> config) to the method.
#Transactional(rollbackFor=Exception.class)
public int insertPnt(Movimenti value) {
try (SqlSession session = sqlSessionFactory.openSession()) {
..............
}
Does the transaction manager take care of the session resourse? Or I have to remove the try-catch clause.
Thk
I post the same question to the MyIbatis-user group see. The response was useful:
To use MyBatis you need either an SqlSesion or a Mapper interface.
With "classic" MyBatis you get the sessions out of an
SqlSessionFactory but when using Spring this changes.
So I follow the example of the documentation:
I inject the Service class with a sqlSessionTemplate that internally takes care of opening and close the session.
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" >
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:myibatis.xml"/>
<property name="mapperLocations" value="classpath*:mappers/*.xml" />
</bean>
<bean id="asTableService" class="com.sirio.cisl.dal.AsTableService">
<property name="session" ref="sqlSession" />
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" destroy-method="clearCache">
<constructor-arg index="0" ref="sqlSessionFactory" />
<constructor-arg index="1" value="BATCH" />
</bean>
Then I can use the #Transactional annotation :
#Transactional(rollbackFor=Exception.class)
public int insertPnt(Movimenti value) {
session.create(......)
}
Davide
I am newbie to Spring and Shiro platforms.
I have two url sets /admin/-- and /vendor/--. Both client sets are authenticating with specific realms. I have extended ModularRealmAuthenticator class to choose correct realm for authenticating.
ModularRealmAuthenticator.java
#Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
MultiLoginAuthenticationToken mlat = null;
Realm loginRealm = null;
if (!(authenticationToken instanceof MultiLoginAuthenticationToken)) {
throw new AuthenticationException("Unrecognized token , not a typeof MultiLoginAuthenticationToken ");
} else {
mlat = (MultiLoginAuthenticationToken) authenticationToken;
logger.debug("realm name is : {}", mlat.getRealmName());
loginRealm = lookupRealm(mlat.getRealmName());
}
return doSingleRealmAuthentication(loginRealm, mlat);
}
protected Realm lookupRealm(String realmName) throws AuthenticationException {
Collection<Realm> realms = getRealms();
for (Realm realm : realms) {
if (realm.getName().equalsIgnoreCase(realmName)) {
logger.debug("look up realm name is : {}", realm.getName());
return realm;
}
}
throw new AuthenticationException("No realm configured for Client " + realmName);
}
But while I am assigning role and permissions from different set of datasource to both clients (Admin and vendor). It is iterating the realms in order which I have defined in applicationContext.xml file.
My ApplicationContext.xml
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="authenticator">
<bean class="com.yatra.mp.security.MultiLoginAuthenticator"/>
</property>
<!-- Single realm app (realm configured next, below). If you have multiple
realms, use the 'realms' property instead. -->
<property name="realms">
<util:list>
<ref bean="adminAuthRealm" />
<ref bean="vendorAuthRealm" />
</util:list>
</property>
<property name="cacheManager" ref="cacheManager" />
</bean>
In both of realms are extending AuthorizingRealm class and both have doGetAuthorizationInfo and doGetAuthenticationInfo method. In which I have defined my custom implementation.
Is it necessary to extend ModularRealmAuthorizer class? If yes, could you please tell me which method I have override?
What you can do is add the domain info to the PrincipalCollection you can wrap in the AuthenticationInfo. It is an added token in the principal collection that gets carried over in subsequent shiro calls. You can use that info in the authentication to skip if it doesn't match your realm. This is actually what we do in our custom realm:
public class OurRealmImpl extends AuthorizingRealm
...
#Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
... //check if user exists and read passwordhash
Login ourLoginToken = ...
SimplePrincipalCollection principalCollection = new SimplePrincipalCollection(ourLoginToken, realmName);
return new SimpleAuthenticationInfo(principalCollection, passwordHash);
}
#Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Collection collection = principals.fromRealm(realmName);
if (collection.isEmpty()) {
return null;
}
Login login = (Login) collection.iterator().next();
... get the rights and return authorization
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissionStrings);
return info;
}
i met that problem too. finally, i solved it. step as the follow:
1) let a new class to extend the ModularRealmAuthorizer.
public class OurModularRealmAuthorizer extend ModularRealmAuthorizer{
private map<string,OurAuthorizerRealm> mRealms;
private map<string,OurAuthorizerRealm> getMRealms(){return mRealms;}
private void setMRealms(map<string,OurAuthorizerRealm> mrealms){
this.mRealms = mrealms;
Collection<Realm> tmpRealms = new ArrayList<Realm>();
for (OurAuthorizerRealm value : mrealms.values()) {
Realm realm = (Realm) value;
tmpRealms.add(realm);
}
this.realms = tmpRealms;/*setting realms*/
}
}
2. spring-shiro.xml:
<bean id="ourModularRealmAuthorizer" class="xx.xxx.shiro.realm.ShiroModularRealmAuthorizer">
<property name="mRealms">
<map>
<entry key="ourAuthorizerRealm1" value-ref="ourAuthorizerRealm1" />
<entry key="ourAuthorizerRealm2" value-ref="ourAuthorizerRealm2" />
</map>
</property>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="authenticator" ref="ourModularRealmAuthenticator"></property>
<property name="authorizer" ref="ouModularRealmAuthorizer"></property>
<property name="cacheManager" ref="shiroCacheManager"></property>
</bean>
all right.
I am developing a web application with spring mvc framework and java. I am using active directory user authentication in the application.
Now i want to assign role to these users from a sql database. So the whole authentication process will be done by taking information from two sources: [ user id & password from active directory & roles of user from sql database ].
I have searched on internet to get any tutorial/samples to do so but I'm unable to get any useful stuff. So any help on the issue will be appreciated.
Here is the current code of my spring security file,
<bean id="adAuthenticationProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<constructor-arg value="abbl.org"/>
<constructor-arg value="LDAP://abbl.org"/>
<property name="convertSubErrorCodesToExceptions" value="true"/>
<property name="useAuthenticationRequestCredentials" value="true"/>
</bean>
<bean id="customAuthenticationProvider"
class="org.abbl.exhbp.templates.CustomAuthorityProvider">
<constructor-arg ref="adAuthenticationProvider"/>
</bean>
<security:authentication-manager>
<security:authentication-provider ref="customAuthenticationProvider"/>
</security:authentication-manager>
my authenticate function in AuthenticationProvider class,
public Authentication authenticate(Authentication authentication) {
final Authentication a = delegate.authenticate(authentication);
// Load additional authorities and create an Authentication object
final List<GrantedAuthority> authorities = getGrantedAuthorities(a.getName());
return new AbstractAuthenticationToken(authorities) {
#Override
public Object getCredentials() {
//return null; //To change body of implemented methods use File | Settings | File Templates.
throw new UnsupportedOperationException();
}
#Override
public Object getPrincipal() {
//return null; //To change body of implemented methods use File | Settings | File Templates.
return a.getPrincipal();
}
};
}
After login , i am getting this exception,
java.lang.UnsupportedOperationException
org.abbl.exhbp.templates.CustomAuthorityProvider$1.getCredentials(CustomAuthorityProvider.java:41)
org.springframework.security.authentication.AbstractAuthenticationToken.eraseCredentials(AbstractAuthenticationToken.java:108)
org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:186)
org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.attemptAuthentication(UsernamePasswordAuthenticationFilter.java:94)
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:195)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:105)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:259)
What modification or implementation I need to do here to achieve what i mentioned above?
Finally i achieved what i aimed for by using UsernamePasswordAuthenticationToken instead of AbstractAuthenticationToken. Solution is given below,
1) spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<security:http auto-config="true" use-expressions="true">
<security:anonymous enabled="false"/>
<security:form-login login-page="/login" default-target-url="/home"
login-processing-url="/j_spring_security_check"
authentication-failure-url="/loginfailed"/>
<security:logout logout-success-url="/logout"/>
</security:http>
<bean id="adAuthenticationProvider"
class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<constructor-arg value="DOMAIN"/>
<constructor-arg value="URL"/>
<property name="convertSubErrorCodesToExceptions" value="true"/>
<property name="useAuthenticationRequestCredentials" value="true"/>
</bean>
<bean id="customAuthenticationProvider"
class="org.abbl.exhbp.templates.CustomAuthorityProvider">
<constructor-arg ref="adAuthenticationProvider"/>
</bean>
<security:authentication-manager>
<security:authentication-provider ref="customAuthenticationProvider"/>
</security:authentication-manager>
2) CustomAuthorityProvider.class
public class CustomAuthorityProvider implements AuthenticationProvider {
private AuthenticationProvider delegate;
public CustomAuthorityProvider(AuthenticationProvider delegate) {
this.delegate = delegate;
}
public Authentication authenticate(Authentication authentication) {
final Authentication a = delegate.authenticate(authentication);
// Load additional authorities and create an Authentication object
final List<GrantedAuthority> authorities = getGrantedAuthorities(a.getName());
return new UsernamePasswordAuthenticationToken(a.getPrincipal(),a.getCredentials(),authorities);
}
#Override
public boolean supports(Class<?> authentication) {
return delegate.supports(authentication);
}
List<GrantedAuthority> getGrantedAuthorities(String username) {
JdbcTemplateDataSource ds = new JdbcTemplateDataSource();
List<GrantedAuthority> roles = ds.getJdbcTemplate().query("select r.Role from Users u join UserRole ur on u.UserId = "
+ "ur.UserId join Roles r on r.RoleId = ur.RoleId where Username = ?",
new String[]{username},
new RowMapper<GrantedAuthority>() {
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException {
return new SimpleGrantedAuthority(rs.getString(1));
}
});
return roles;
}
}