Hi I am using Spring web service Here is how my xsd looks
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.test-software.co.uk/abcportal/schema/abcportaltokendetailsws"
targetNamespace="http://www.test-software.co.uk/abcportal/schema/abcportaltokendetailsws"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:element name="abcPortalTokenDetailsRequest">
<xs:complexType>
<xs:annotation>
<xs:documentation>The data structure required to get token details</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="token" type="token3rdParty"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="abcPortalTokenDetailsResponse">
<xs:complexType>
<xs:sequence>
<xs:element name="tokenDetails" type="TokenDetailsType" />
<xs:element name="result" type="resultStructure"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:simpleType name="token3rdParty">
<xs:restriction base="xs:token">
<xs:minLength value="1"></xs:minLength>
<xs:maxLength value="60"></xs:maxLength>
<xs:pattern value="([A-Za-z0-9]+)" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="TokenDetailsType">
<xs:annotation>
<xs:documentation>token details</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element name="sNumber" type="SNumberType"/>
<xs:element name="sId" type="SIdType"/>
<xs:element name="sName" type="SNameType"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="SNumberType">
<xs:restriction base="xs:string">
<xs:minLength value="1"></xs:minLength>
<xs:maxLength value="60"></xs:maxLength>
<xs:pattern value="([A-Za-z0-9]+)" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="SIdType">
<xs:restriction base="xs:string">
<xs:minLength value="1"></xs:minLength>
<xs:maxLength value="2"></xs:maxLength>
<xs:pattern value="([A-Za-z]+)" />
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="SNameType">
<xs:restriction base="xs:string">
<xs:minLength value="1"></xs:minLength>
<xs:maxLength value="128"></xs:maxLength>
<xs:pattern value="([A-Za-z0-9]+)" />
</xs:restriction>
</xs:simpleType>
<xs:complexType name="resultStructure">
<xs:sequence>
<xs:element name="resultCode" minOccurs="1" maxOccurs="1">
<xs:annotation>
<xs:documentation>The result code indicates the outcome of the available list for request.</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value="OK"/>
<xs:enumeration value="INVALID"/>
<xs:enumeration value="ERROR"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="resultMessage" minOccurs="1" maxOccurs="1">
<xs:annotation>
<xs:documentation>The associated message for this result</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="255"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>
Here is the Endpoint
#Endpoint
public class abcPortalTokenDetailsEndpoint {
private IabcPortalManager abcPortalManager;
private static final Logger logger = Logger.getLogger(abcPortalTokenDetailsEndpoint.class);
public void setAbcPortalManager(IabcPortalManager abcPortalManager) {
this.abcPortalManager = abcPortalManager;
}
#PayloadRoot(localPart= "AbcPortalTokenDetailsRequest", namespace="http://www.test-software.co.uk/abcportal/schema/abcportaltokendetailsws")
public abcPortalTokenDetailsResponse doit(abcPortalTokenDetailsRequest request){
abcPortalTokenDetailsResponse response = new abcPortalTokenDetailsResponse();
// check that the token is currently valid
String token = request.getToken().trim();
String resultCode = "ERROR", resultMessage = "Internal error occured - valid response could not be generated";
boolean okResult = true, valid = false;
TAuth tp = null;
Integer sId = 0;
String sName = "";
String errorMsg = null;
try {
//validate token
if (token != null) {
resultCode = "OK";
resultMessage = "Valid session";
} else {
resultCode = "INVALID";
resultMessage = "No record of user being logged in";
okResult = false;
}
} catch (DataAccessException ex) {
String fmtStr = "Could not determine whether end user token (%s) is valid.\n %s";
String errMsg = String.format(fmtStr, token, ex.getMessage());
okResult = false;
logger.error(errMsg);
assert(false) : errMsg;
}
if(okResult){
if(logger.isDebugEnabled()){
logger.debug("abcPortalTokenDetailsResponse Authenticate user" );
}
tp = abcPortalManager.getTpAuth(token);
if(logger.isDebugEnabled()){
if(tp != null){
if (tp.getSId()!= null){
sName = "ct"; abcPortalManager.getSName(tp.getSId());
}
} else {
logger.debug("abcPortalTokenDetailsResponse tparty details not found");
}
}
valid = true;
}
TokenDetailsType tokenDetailsPart = constructResponseTokenDetailsPart(valid, okResult, tp, sName);
response.setTokenDetails(tokenDetailsPart);
ResultStructure resultPart = constructResponseResultPart(valid, okResult, errorMsg);
response.setResult(resultPart);
return response;
}
private TokenDetailsType constructResponseTokenDetailsPart(
boolean valid, boolean okResult, TAuth tp,String sName) {
TokenDetailsType tdt = null;
if (valid && okResult) {
tdt = new TokenDetailsType();
tdt.setSId(tp.getSId());
tdt.setSNumber(tp.getSNumber);
tdt.setSName(sName);
}
return tdt;
}
/**
*
* #param response
* #param okResult
* void
*
*/
private ResultStructure constructResponseResultPart(
boolean valid, boolean okResult, String errorMessage) {
// Determine result part of response
String resultCode, resultMessage;
if (okResult) {
resultCode = (valid) ? "OK" : "INVALID";
resultMessage = (valid) ? "Successfull query" : "User not authorised";
} else {
resultCode = "ERROR";
resultMessage = "Valid response could not be generated: " + errorMessage;
}
ResultStructure resp_result = new ResultStructure();
resp_result.setResultCode(resultCode);
resp_result.setResultMessage(resultMessage);
return resp_result;
}
}
when i try to test the webservice using SoapUI why response has multiple namespace of different webservices within my project??? i am expecting just abcportaltokendetailsws and not other services like abcportalloginstatusws ,abcportallogoutws not sure why this is displayed?
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns10:abcPortalTokenDetailsResponse
xmlns:ns10="http://www.test-software.co.uk/abcportal/schema/abcportaltokendetailsws"
xmlns:ns2="http://www.test-software.co.uk/abcportal/schema/abcportalloginstatusws" xmlns:ns4="http://www.test-software.co.uk/abcportal/schema/abcportallpaymentws" xmlns:ns5="http://www.test-software.co.uk/abcportal/schema/abcportallogoutws" xmlns:ns6="http://www.test-software.co.uk/abcportal/schema/abcportalfundservicews" xmlns:ns7="http://www.test-software.co.uk/abcportal/schema/abcportalpayservicews"
<ns10:tokenDetails>
<ns10:sNumber>43454</ns10:sNumber>
<ns10:sId/>
<ns10:sName>Bridge Market</ns10:sName>
</ns10:tokenDetails>
<ns10:result>
<ns10:resultCode>OK</ns10:resultCode>
<ns10:resultMessage>Successfull query</ns10:resultMessage>
</ns10:result>
</ns10:abcPortalTokenDetailsResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This is possible to avoid using Spring 2.5 along with spring-ws 1.5.9 abcPortalTokenDetailsEndpoint can extend MarshallingMethodEndpointAdapter. Also in the config file if we have different beanid marshallers for each end points it works fine. I also tried the same thing in Spring 3.0 and spring-ws 1.5.9 officially not supported , it does not work with multiple beanid marshaller, it needs the word marshaller in the bean id ???
example
the whole problem is using contextpath with ":" or "list" (this adds all the namesapce in the response)
<oxm:jaxb2-marshaller id="marshaller" contextPath="com.abc.web.ws.login.status.schema:com.abc.web.ws.profile.schema:com.abc.web.ws.tokendetails.schema>
if we change the above code to as following it works in Spring 2.5.6 not spring 3.0 why???
<bean id="abcPortalTokenDetailsEndpoint" class="com.abc.web.ws.tokendetails.abcPortalTokenDetailsEndpoint">
<property name="marshaller" ref="marshaller1"/>
<property name="unmarshaller" ref="marshaller1"/>
</bean>
<bean id="abcPortalTrustedAccessLogInEndpoint" class="com.abc.web.ws.login.trustedaccess.abcPortalTrustedAccessLogInEndpoint">
<property name="marshaller" ref="marshaller2"/>
<property name="unmarshaller" ref="marshaller2"/>
</bean>
<bean id="abcPortalThirdPartyLogInEndpoint" class="com.abc.web.ws.login.thirdparty.abcPortalThirdPartyLogInEndpoint">
<property name="marshaller" ref="marshaller"/>
<property name="unmarshaller" ref="marshaller"/>
</bean>
<bean id="marshaller1" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.abc.web.ws.tokendetails.schema"/>
</bean>
<bean id="marshaller2" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.abc.web.ws.login.trustedaccess.schema"/>
</bean>
<bean id="marshaller3" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="contextPath" value="com.abc.web.ws.login.thirdparty.schema"/>
</bean>
public class abcPortalTokenDetailsEndpoint extends MarshallingMethodEndpointAdapter{
private IabcPortalManager abcPortalManager;
private static final Logger logger = Logger.getLogger(abcPortalTokenDetailsEndpoint.class);
public void setAbcPortalManager(IabcPortalManager abcPortalManager) {
this.abcPortalManager = abcPortalManager;
}
public abcPortalTokenDetailsResponse handleabcPortalTokenDetailsRequest(abcPortalTokenDetailsRequest request){
abcPortalTokenDetailsResponse response = new abcPortalTokenDetailsResponse();
// check that the token is currently valid
String token = request.getToken().trim();
String resultCode = "ERROR", resultMessage = "Internal error occured - valid response could not be generated";
boolean okResult = true, valid = false;
TAuth tp = null;
Integer sId = 0;
String sName = "";
String errorMsg = null;
try {
//validate token
if (token != null) {
resultCode = "OK";
resultMessage = "Valid session";
} else {
resultCode = "INVALID";
resultMessage = "No record of user being logged in";
okResult = false;
}
} catch (DataAccessException ex) {
String fmtStr = "Could not determine whether end user token (%s) is valid.\n %s";
String errMsg = String.format(fmtStr, token, ex.getMessage());
okResult = false;
logger.error(errMsg);
assert(false) : errMsg;
}
if(okResult){
if(logger.isDebugEnabled()){
logger.debug("abcPortalTokenDetailsResponse Authenticate user" );
}
tp = abcPortalManager.getTpAuth(token);
if(logger.isDebugEnabled()){
if(tp != null){
if (tp.getSId()!= null){
sName = "ct"; abcPortalManager.getSName(tp.getSId());
}
} else {
logger.debug("abcPortalTokenDetailsResponse tparty details not found");
}
}
valid = true;
}
TokenDetailsType tokenDetailsPart = constructResponseTokenDetailsPart(valid, okResult, tp, sName);
response.setTokenDetails(tokenDetailsPart);
ResultStructure resultPart = constructResponseResultPart(valid, okResult, errorMsg);
response.setResult(resultPart);
return response;
}
No need to have separate marshaller. Can be solved by having the Response method in your Endpoint annotated as #ResponsePayload.
Ex:
#PayloadRoot(localPart= "AbcPortalTokenDetailsRequest", namespace="http://www.test-software.co.uk/abcportal/schema/abcportaltokendetailsws")
#ResponsePayload
public abcPortalTokenDetailsResponse doit(abcPortalTokenDetailsRequest request){
Related
I am using jersey and spring-oauth2 with spring security. My app is working fine with end points "/oauth/token".
I want to change the endpoints to accept more data. The requirement is, I want to send more details to the token API (i.e. the device details OS, phone/tablet/web etc.). So, I want to override the endpoint and if authentication is successful, I want to store that extra information in database.
I could not find anything related to changing the API in such a way.
Can someone help?
I have found a solution by writing a wrapper controller and assigning default tokenEndpoint bean
#FrameworkEndpoint
public class LoginContrller{
private static Logger logger = org.slf4j.LoggerFactory.getLogger(LoginContrller.class);
private WebResponseExceptionTranslator providerExceptionHandler = new DefaultWebResponseExceptionTranslator();
#Autowired
private UserManager userManager;
#Autowired
TokenEndpoint tokenEndPoint;
#RequestMapping(value = "/user/login", method=RequestMethod.POST,consumes=MediaType.APPLICATION_JSON)
public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, #RequestParam
Map<String, String> parameters,#RequestBody(required=false) LoginModel loginModel) throws HttpRequestMethodNotSupportedException {
ResponseEntity<OAuth2AccessToken> response = tokenEndPoint.postAccessToken(principal, parameters);
if(!isRefreshTokenRequest(parameters)){
if(loginModel!=null){
loginModel.setUsername(parameters.get("username"));
try {
userManager.loginUser(loginModel);
} catch (UserNotFoundException e) {
logger.warn("Exception in custom login {} ",e);
}
}
}
return response;
}
private boolean isRefreshTokenRequest(Map<String, String> parameters) {
return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;
}
private boolean isAuthCodeRequest(Map<String, String> parameters) {
return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;
}
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public void handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
throw e;
}
#ExceptionHandler(Exception.class)
public ResponseEntity<OAuth2Exception> handleException(Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(e);
}
#ExceptionHandler(ClientRegistrationException.class)
public ResponseEntity<OAuth2Exception> handleClientRegistrationException(Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(new BadClientCredentialsException());
}
#ExceptionHandler(OAuth2Exception.class)
public ResponseEntity<OAuth2Exception> handleException(OAuth2Exception e) throws Exception {
logger.info("Handling error: " + e.getClass().getSimpleName() + ", " + e.getMessage());
return getExceptionTranslator().translate(e);
}
private WebResponseExceptionTranslator getExceptionTranslator() {
return providerExceptionHandler;
}
}
Change in web.xml : just replace the URL with new one
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/user/login</url-pattern>
</servlet-mapping>
And finally create bean with logincontroller class and change the URL in spring-security.xml.
Change the oauth token url and url of clientCredentialsTokenEndpointFilter as mentioned below.
<sec:http pattern="/user/login" create-session="stateless" authentication-manager-ref="clientAuthenticationManager" use-expressions="true" >
<sec:intercept-url pattern="/user/login" access="isFullyAuthenticated()"/>
<sec:csrf disabled="true"/>
<sec:anonymous enabled="false" />
<sec:http-basic entry-point-ref="clientAuthenticationEntryPoint" />
<sec:custom-filter ref="clientCredentialsTokenEndpointFilter" after="BASIC_AUTH_FILTER" />
</sec:http>
<bean id="clientCredentialsTokenEndpointFilter" class="org.springframework.security.oauth2.provider.client.ClientCredentialsTokenEndpointFilter">
<constructor-arg value="/user/login"></constructor-arg>
<property name="authenticationManager" ref="clientAuthenticationManager" />
<property name="filterProcessesUrl" value="/user/login" />
</bean>
<bean class="com.oauth2.provider.endpoint.LoginContrller" />
We are migrating from Spring Security 2.0 to 3.1.
In Spring Security 2.0 the concurrency control was configured adding the following line in the configuration file:
<concurrent-session-control max-sessions="1" />
Now, with 3.1, we added the following:
<session-management invalid-session-url="/index.jsf">
<concurrency-control max-sessions="1" />
</session-management>
We make the login from a backing bean. The code is the following:
public String login(){
Authentication authenticationResponseToken = authenticate();
if (authenticationResponseToken != null && authenticationResponseToken.isAuthenticated()) {
return NavigationConstants.PORTAL_REDIRECT_USER;
}
else{
return NavigationConstants.PORTAL_LOGIN + sessionManagedBean.getUrlQuery();
}
}
private Authentication authenticate(){
debug("authenticate");
AuthenticationManager authenticationManager = (AuthenticationManager) UtilidadesFaces.getSpringBean("authManager");
//simple token holder
Authentication authenticationRequestToken = createAuthenticationToken(sessionManagedBean);
Authentication authenticationResponseToken = null;
//authentication action
try {
authenticationResponseToken = authenticationManager.authenticate(authenticationRequestToken);
SecurityContextHolder.getContext().setAuthentication(authenticationResponseToken);
List<GrantedAuthority> authorities = (List<GrantedAuthority>) authenticationResponseToken.getAuthorities();
if(authorities.size() > 0){
this.sessionManagedBean.setRole(authorities.get(0).getAuthority());
}
CustomUser customUser = (CustomUser) authenticationResponseToken.getPrincipal();
this.sessionManagedBean.setIdCl(customUser.getIdCl());
} catch (BadCredentialsException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (AuthenticationServiceException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (LockedException lockedException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.lockedException", this.sessionManagedBean.getActualLanguage());
} catch (DisabledException disabledException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.disabledException", this.sessionManagedBean.getActualLanguage());
}
return authenticationResponseToken;
}
private Authentication createAuthenticationToken(SessionManagedBean sessionManagedBean) {
String username = sessionManagedBean.getUsername() + sessionManagedBean.getIdGuest();
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(username, sessionManagedBean.getPassword());
return usernamePasswordAuthenticationToken;
}
We already have implemented the equals() and hashCode() methods in the class which implements UserDetails.
No concurrency control is working now, one user can login several times and work with all the sessions simoultaneously.
Any help would be appreciated.
I am using spring 3.2.1 , i just did the following
// spring will inject ConcurrentSessionControlAuthenticationStrategy by default
#Autowired
SessionAuthenticationStrategy sessionAuthenticationStrategy;
and inside your authentication method i just called
sessionAuthenticationStrategy.onAuthentication(authenticationResponseToken, httpReq, httpResp);
And in security.xml file
<session-management invalid-session-url="/login.jsp?time=1">
<concurrency-control error-if-maximum-exceeded="false" max-sessions="1" expired-url="/logout"/>
</session-management>
As all these are default values i think we dont need to set it.
This did the work for me.
Try this..according to me its work
<session-management session-fixation-protection="newSession">
<concurrency-control error-if-maximum-exceeded="true" max-sessions="1" expired-url="/loginexpired" />
</session-management>
Finally i solved the issue calling SessionAuthenticationStrategy#onAuthentication and adding some spring security filters.
My code:
<http auto-config="false" use-expressions="true">
<session-management session-authentication-strategy-ref="sas" invalid-session-url="/index.jsf" />
......
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
</http>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" />
</beans:bean>
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/index.jsf" />
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
....
and adding sessionAuthenticationStrategy.onAuthentication()...
private Authentication authenticate(){
debug("authenticate");
AuthenticationManager authenticationManager = (AuthenticationManager) UtilidadesFaces.getSpringBean("authManager");
//simple token holder
Authentication authenticationRequestToken = createAuthenticationToken(sessionManagedBean);
Authentication authenticationResponseToken = null;
//authentication action
try {
authenticationResponseToken = authenticationManager.authenticate(authenticationRequestToken);
SecurityContextHolder.getContext().setAuthentication(authenticationResponseToken);
List<GrantedAuthority> authorities = (List<GrantedAuthority>) authenticationResponseToken.getAuthorities();
if(authorities.size() > 0){
this.sessionManagedBean.setRole(authorities.get(0).getAuthority());
}
CustomUser customUser = (CustomUser) authenticationResponseToken.getPrincipal();
this.sessionManagedBean.setIdCl(customUser.getIdCl());
HttpServletRequest httpReq = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
HttpServletResponse httpResp = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
SessionAuthenticationStrategy sessionAuthenticationStrategy = (SessionAuthenticationStrategy) UtilidadesFaces.getSpringBean("sas");;
sessionAuthenticationStrategy.onAuthentication(authenticationResponseToken, httpReq, httpResp);
} catch (BadCredentialsException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (AuthenticationServiceException badCredentialsException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.badCredentialsException", this.sessionManagedBean.getActualLanguage());
} catch (LockedException lockedException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.lockedException", this.sessionManagedBean.getActualLanguage());
} catch (DisabledException disabledException) {
UtilidadesFaces.addMessage(null, FacesMessage.SEVERITY_ERROR, "login.error.disabledException", this.sessionManagedBean.getActualLanguage());
}
return authenticationResponseToken;
}
I use Spring Security to authenticate a user against an Active Directory server. A CustomUserContext is also injected into the ldapAuthenticationProvider bean to provide access to additional LDAP attributes. Everything works quite well. I have no problem pulling whatever I want from the authenticated user.
The issue I have is that I want to retrieve some attributes, most specifically the email address, from the Active Directory server on a user other than the user that is logged in. Is it possible to achieve this by leveraging what I already have, or is my only option to use a totally separate method to access LDAP attributes from a different user?
[edit]
Configuration follows
security-config.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:sec="http://www.springframework.org/schema/security"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
<property name="url" value="ldap://xxxx.xxxx.xxx:389" />
<property name="base" value="dc=corp,dc=global,dc=xxxxx,dc=com" />
<property name="userDn" value="CN=lna.authquery,OU=LDAPGroups,OU=NorthAmerica,DC=corp,DC=global,DC=xxxxx,DC=com" />
<property name="password" value="xxxxxxx" />
<property name="pooled" value="true" />
<!-- AD Specific Setting for avoiding the partial exception error -->
<property name="referral" value="follow" />
</bean>
<bean id="ldapAuthenticationProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider" >
<constructor-arg>
<bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="contextSource" />
<property name="userSearch">
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg index="0" value="" />
<constructor-arg index="1" value="(sAMAccountName={0})" />
<constructor-arg index="2" ref="contextSource" />
</bean>
</property>
</bean>
</constructor-arg>
<constructor-arg>
<bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator">
<constructor-arg ref="contextSource" />
<constructor-arg value="" />
<property name="groupSearchFilter" value="(member={0})" />
<property name="searchSubtree" value="true" />
<!-- Settings below convert the adds the prefix ROLE_ to roles returned from AD -->
</bean>
</constructor-arg>
<property name="userDetailsContextMapper">
<bean class="net.xxxx.xxxxx.utilities.CustomUserDetailsContextMapper" />
</property>
</bean>
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<constructor-arg>
<list>
<ref local="ldapAuthenticationProvider" />
</list>
</constructor-arg>
</bean>
<sec:http pattern="/css/**" security="none"/>
<sec:http pattern="/images/**" security="none"/>
<sec:http auto-config="true" authentication-manager-ref="authenticationManager" >
<sec:intercept-url pattern="/login.jsp*" requires-channel="https" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
<sec:intercept-url pattern="/**" requires-channel="https" access="IS_AUTHENTICATED_FULLY"/>
<sec:form-login login-page='/login.jsp'
default-target-url="/home.html"
authentication-failure-url="/login.jsp" />
</sec:http>
CustomeUserDetails.java
package net.xxxx.xxxx.utilities;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
public class CustomUserDetails extends User {
private static final long serialVersionUID = 1416132138315457558L;
// extra instance variables
final String fullname;
final String email;
final String title;
public CustomUserDetails(String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities, String fullname,
String email, String title) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
this.fullname = fullname;
this.email = email;
this.title = title;
}
public String getFullname() {
return this.fullname;
}
public String getEmail() {
return this.email;
}
public String getTitle() {
return this.title;
}
}
CustomUserDetailsContextMapper.java
package net.xxxx.xxxxx.utilities;
import java.util.Collection;
public class CustomUserDetailsContextMapper implements UserDetailsContextMapper {
public UserDetails mapUserFromContext(DirContextOperations ctx,
String username, Collection<? extends GrantedAuthority> authorities) {
String fullname = "";
String email = "";
String title = "";
Attributes attributes = ctx.getAttributes();
try {
fullname = (String) attributes.get("displayName").get();
email = (String) attributes.get("mail").get();
title = (String) attributes.get("title").get();
} catch (NamingException e) {
e.printStackTrace();
}
CustomUserDetails details = new CustomUserDetails(username, "", true, true, true, true, authorities, fullname, email, title);
return details;
}
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
I finally did end up figuring out how to do this. I'm answering this in case it helps someone else who needs to do this. I'd be surprised if I'm the only one.
First I had to move my security-config.xml file out of the WEB-INF structure and put it under the spring resources directory. The contextSource bean I was able to reuse. However I could not reuse the CustomUserDetailsContextMapper.java nor the CustomUserDetails.java class as they were too specific to Spring security and not to just retrieving LDAP data from an unauthenticated user.
I ended up writing a separate class for the LDAP access that had the common contextSource autowired in. That class is below.
LdapDao.java
package net.xxxxx.xxx.dao;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javax.naming.directory.Attributes;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.stereotype.Component;
#Component
public class LdapDao {
LdapTemplate template;
#Autowired
public LdapDao(LdapContextSource contextSource) {
template = new LdapTemplate(contextSource);
}
#SuppressWarnings("unchecked")
public Map<String, String> getUserAttributes(String username) {
Map<String, String> results = new HashMap<String, String>();
String objectClass = "samAccountName=" + username;
LinkedList<Map<String, String>> list = (LinkedList<Map<String, String>>) template.search("", objectClass, new UserAttributesMapper());
if (!list.isEmpty()) {
// Should only return one item
results = list.get(0);
}
return results;
}
private class UserAttributesMapper implements AttributesMapper {
#Override
public Map<String, String> mapFromAttributes(Attributes attributes) throws javax.naming.NamingException {
Map<String, String> map = new HashMap<String, String>();
String fullname = (String) attributes.get("displayName").get();
String email = (String) attributes.get("mail").get();
String title = (String) attributes.get("title").get();
map.put("fullname", fullname);
map.put("email", email);
map.put("title", title);
return map;
}
}
}
#Bill what you've done is great, though there is actually an easier way. Instead of resorting to the LdapTemplate, just use the beans you've already registered for DefaultLdapAuthoritiesPopulator and FilterBasedLdapUserSearch. This way you can get the same UserDetails object which also has the authorities populated and reuses your existing code for your net.xxxx.xxxxx.utilities.CustomUserDetailsContextMapper.
Here's what you need to do:
Split out the beens you need to inject as named beans and use ref attributes for the properties and constructor-args (DefaultLdapAuthoritiesPopulator, FilterBasedLdapUserSearch, net.xxxx.xxxxx.utilities.CustomUserDetailsContextMapper).
In your LdapDao inject references to:
FilterBasedLdapUserSearch - userSearch
DefaultLdapAuthoritiesPopulator - authPop
net.xxxx.xxxxx.utilities.CustomUserDetailsContextMapper - userMapper
Add the following method to your LdapDao:
.
public UserDetails getUserDetails(final String username) {
try {
DirContextOperations ctx = userSearch.searchForUser(username);
return userMapper.mapUserFromContext(ctx, username,
authPop.getGrantedAuthorities(ctx, username));
} catch (UsernameNotFoundException ex) {
return null;
}
}
Now you can just call getUserDetails(String) to get the same object you do when retrieving the currently logged in context, and can use the same code etc.
I have a simple Apache CXF webservice with the following beans.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns="http://www.springframework.org/schema/security"
xmlns:ssec="http://cxf.apache.org/spring-security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd
http://cxf.apache.org/spring-security
http://cxf-spring-security.googlecode.com/svn/trunk/cxf-spring-security/src/main/resources/schemas/spring-security.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd">
<beans:import resource="classpath:META-INF/cxf/cxf.xml" />
<http auto-config='true' >
<http-basic/>
<anonymous enabled="false"/>
</http>
<beans:bean id="methodSecurityInterceptor"
class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<beans:property name="authenticationManager" ref="authenticationManager"/>
<beans:property name="accessDecisionManager" ref="accessDecisionManager"/>
<beans:property name="securityMetadataSource">
<beans:value>
org.mycompany.com.CxfSpringSecuredService.HelloWorldImpl.sayHi=ROLE_OPERATOR
org.mycompany.com.CxfSpringSecuredService.HelloWorldImpl.sayHiAdmin*=ROLE_ADMIN,ROLE_SUPERVISOR
org.mycompany.com.CxfSpringSecuredService.HelloWorldImpl.deleteAccounts*=ROLE_SUPERVISOR
</beans:value>
</beans:property>
</beans:bean>
<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<beans:property name="decisionVoters">
<beans:list>
<beans:bean class="org.springframework.security.access.vote.RoleVoter" />
</beans:list>
</beans:property>
</beans:bean>
<authentication-manager alias="authenticationManager">
<authentication-provider>
<user-service>
<user name="operator" password="operator" authorities="ROLE_OPERATOR" />
<user name="admin" password="admin" authorities="ROLE_ADMIN" />
<user name="sup" password="sup" authorities="ROLE_SUPERVISOR" />
</user-service>
</authentication-provider>
</authentication-manager>
<jaxws:endpoint
id="helloWorld"
implementor="org.mycompany.com.CxfSpringSecuredService.HelloWorldImpl"
address="/HelloWorld" />
</beans:beans>
My webservice implementation is the following three simple methods:
#WebService(endpointInterface = "org.mycompany.com.CxfSpringSecuredService.HelloWorld")
public class HelloWorldImpl implements HelloWorld {
public String sayHi(String text) {
SecurityContext context = SecurityContextHolder.getContext();
if (context != null){
Authentication authentication = context.getAuthentication();
if (authentication != null){
Collection<GrantedAuthority> roles = authentication.getAuthorities();
if (roles != null){
GrantedAuthority[] authorities = new GrantedAuthority[roles.size()];
roles.toArray(authorities);
for (int i = 0; i < authorities.length; i++)
text = text + " " + authorities[i];
}
}
}
return "Hello " + text;
}
public String sayHiAdmin(){
return "Hello admin";
}
public String deleteAccounts(String name){
return "Accounts deleted by " + name;
}
}
I have a C# client that calls the web service and passes authentication information within the SOAP header. I know that my client is passing authentication information as I get the following message in an exception:
The HTTP request is unauthorized with client authentication scheme 'Anonymous'. The authentication header received from the server was 'Basic realm="Spring Security Application"'.
if I issue invalid credentials. I issue proper credentials I get the correct response for each web service call. So far, so good.
If I pass credential information for operator, and call method deleteAccounts, I expected the same authorized error as above, but webservice method is invoked correctly.
I have looked through the docs here Spring Framework but cannot determine what may be missing.
Any ideas?
TIA.
Edit: Corrected user config.
The default behavior of the Channel Processors is to do a sendRedirect (which is redirect temporary with 302 code). I need to change this behavior so that a permanent (301) redirect is done instead of 302 redirect. I tried to do the following:
Create a custom ChannelProcessingFilter by extending the ChannelProcessingFilter:
public class MyChannelProcessingFilter extends ChannelProcessingFilter{
//No implementation, I needed this to just make sure that a custom filter is created and I can configure it as a custom filter in the xml file.
}
Create a custom EntryPoint by extending the AbstractRetryEntryPoint
public class RetryWithHttpsEntryPoint extends org.springframework.security.web.access.channel.AbstractRetryEntryPoint {
private PortResolver portResolver = new PortResolverImpl();
private final String scheme ="https://";
/** The standard port for the scheme (80 for http, 443 for https) */
private final int standardPort = 443;
public RetryWithHttpsEntryPoint() {
super("https://", 443);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse res) throws IOException, ServletException {
String queryString = request.getQueryString();
String redirectUrl = request.getRequestURI() + ((queryString == null) ? "" : ("?" + queryString));
Integer currentPort = new Integer(portResolver.getServerPort(request));
Integer redirectPort = getMappedPort(currentPort);
if (redirectPort != null) {
boolean includePort = redirectPort.intValue() != standardPort;
redirectUrl = scheme + request.getServerName() + ((includePort) ? (":" + redirectPort) : "") + redirectUrl;
}
if (logger.isDebugEnabled()) {
logger.debug("Redirecting to: " + redirectUrl);
}
res.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
res.setHeader("Location", redirectUrl);
res.setHeader("Connection", "close");
}
protected Integer getMappedPort(Integer mapFromPort) {
return getPortMapper().lookupHttpsPort(mapFromPort);
}
}
Configure the same in the applicationContext-security.xml file. I am putting the complete xml file for your reference (removing the parts that are not needed. If you require the other parts do let me know)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<security:http auto-config="false"
entry-point-ref="authenticationProcessingFilterEntryPoint"
access-decision-manager-ref="accessDecisionManager" >
<security:intercept-url pattern="/activ8/protectedCheckEligibility.html**" access="user" requires-channel="https"/>
<security:intercept-url pattern="/siteMap.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<security:intercept-url pattern="/privacyPolicy.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<!-- other urls configured over here -->
<security:intercept-url pattern="/*.jsp" access="ROLE_ANONYMOUS,admin,user" requires-channel="https"/>
<security:intercept-url pattern="/**/*.html**" access="ROLE_ANONYMOUS,user,admin" requires-channel="https"/>
<security:intercept-url pattern="/fb_activities.html**" access="parent" />
<security:remember-me key="appfuseRocks" />
<security:custom-filter position="SWITCH_USER_FILTER" ref="careSwitchUserProcessingFilter"/>
<security:custom-filter position="FORM_LOGIN_FILTER" ref="myCustomAuthenticationProcessingFilter"/>
<!-- configured the custom channel filter over here -->
<security:custom-filter position="CHANNEL_FILTER" ref="myChannelProcessingFilter"/>
</security:http>
<bean id="myChannelProcessingFilter" class="com.my.webapp.filter.myChannelProcessingFilter">
<property name="channelDecisionManager" ref="channelDecisionManager" />
<property name="securityMetadataSource">
<security:filter-security-metadata-source path-type="ant">
<security:intercept-url pattern="/**" access="REQUIRES_INSECURE_CHANNEL" />
</security:filter-security-metadata-source>
</property>
</bean>
<bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
<property name="channelProcessors">
<list>
<ref bean="secureChannelProcessor"/>
</list>
</property>
</bean>
<bean id="secureChannelProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor">
<property name="entryPoint" ref="secureChannelEntryPoint"/>
<!-- <property name="portMapper" ref="portMapper" /> -->
<property name="secureKeyword" value="REQUIRES_SECURE_CHANNEL"/>
</bean>
<bean id="secureChannelEntryPoint" class="com.my.webapp.filter.RetryWithHttpsEntryPoint"/>
<!-- lot of other configuratons... removed -->
</beans>
I am getting following errors when I try to run my tomcat:
ERROR 2011-12-26 21:13:21,569 [ina].[localhost].[/]]: Exception sending context initialized event to listener instance of class com.kajeet.webapp.listener.StartupListener
org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Filter beans '' and 'Root bean: class [org.springframework.security.web.access.channel.ChannelProcessingFilter]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null' have the same 'order' value. When using custom filters, please make sure the positions do not conflict with default filters. Alternatively you can disable the default filters by removing the corresponding child elements from and avoiding the use of .
Offending resource: ServletContext resource [/WEB-INF/applicationContext-security.xml]
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:68)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:85)
at org.springframework.beans.factory.parsing.ReaderContext.error(ReaderContext.java:72)
at org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.checkFilterChainOrder(HttpSecurityBeanDefinitionParser.java:196)
at org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.parse(HttpSecurityBeanDefinitionParser.java:132)
at org.springframework.security.config.SecurityNamespaceHandler.parse(SecurityNamespaceHandler.java:86)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1335)
at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1325)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:135)
at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:93)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:143)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:178)
at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:149)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:124)
at org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(XmlWebApplicationContext.java:93)
at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:467)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:397)
at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:276)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:197)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47)
at com.kajeet.webapp.listener.StartupListener.contextInitialized(StartupListener.java:51)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3764)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4216)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:760)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:740)
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:544)
at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.java:920)
at org.apache.catalina.startup.HostConfig.deployDirectories(HostConfig.java:883)
at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:492)
at org.apache.catalina.startup.HostConfig.start(HostConfig.java:1138)
at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:311)
at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:120)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1022)
at org.apache.catalina.core.StandardHost.start(StandardHost.java:736)
at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1014)
at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:443)
at org.apache.catalina.core.StandardService.start(StandardService.java:448)
at org.apache.catalina.core.StandardServer.start(StandardServer.java:700)
at org.apache.catalina.startup.Catalina.start(Catalina.java:552)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:295)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:433)
I have also overridden other filters and it does not complain about those. This application was running perfectly fine before. We had this additional requirement and hence I added the new filter and ran into such errors.
Second approach that I tried is just configuring the default ChannelProcessingFilter in the XML, since in Spring 3.0 the filters are automatically called, I was under impression that I can configure them in XML file and spring will automatically load them, but it didn't:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<security:http auto-config="false"
entry-point-ref="authenticationProcessingFilterEntryPoint"
access-decision-manager-ref="accessDecisionManager" >
<security:intercept-url pattern="/activ8/protectedCheckEligibility.html**" access="user" requires-channel="https"/>
<security:intercept-url pattern="/siteMap.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<security:intercept-url pattern="/privacyPolicy.html" access="ROLE_ANONYMOUS,user,admin" requires-channel="http"/>
<!-- other urls configured over here -->
<security:intercept-url pattern="/*.jsp" access="ROLE_ANONYMOUS,admin,user" requires-channel="https"/>
<security:intercept-url pattern="/**/*.html**" access="ROLE_ANONYMOUS,user,admin" requires-channel="https"/>
<security:intercept-url pattern="/fb_activities.html**" access="parent" />
<security:remember-me key="appfuseRocks" />
<security:custom-filter position="SWITCH_USER_FILTER" ref="careSwitchUserProcessingFilter"/>
<security:custom-filter position="FORM_LOGIN_FILTER" ref="myCustomAuthenticationProcessingFilter"/>
</security:http>
<bean id="channelDecisionManager" class="org.springframework.security.securechannel.ChannelDecisionManagerImpl">
<property name="channelProcessors">
<list>
<ref bean="secureChannelProcessor"/>
<ref bean="insecureChannelProcessor"/>
</list>
</property>
</bean>
<bean id="secureChannelProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor"/>
<bean id="insecureChannelProcessor" class="org.springframework.security.web.access.channel.InsecureChannelProcessor"/>
<!-- lot of other configuratons... removed -->
</beans>
Any help will be definitely appreciated. I am not a Spring pro, but I have done some work on it, a pointer or two may definitely help me to resolve this. Thank you in advance
Solution:
The issue is that we cannot have both the security:http as well as the myChannelProcessingFilter (the one I had overridden) to deal with the access argument of the security:intercept-url, hence I removed the http tag and added the access thing in the myChannelProcessingFilter, where I wanted it to process. The XML that resolved it is follows
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<!--
The http element responsible for creating a FilterChainProxy and the filter beans which it uses.
Common problems like incorrect filter ordering are no longer an issue as the filter positions are predefined.
-->
<security:http auto-config="false"
entry-point-ref="authenticationProcessingFilterEntryPoint"
access-decision-manager-ref="accessDecisionManager" >
<security:custom-filter position="CHANNEL_FILTER" ref="channelProcessingFilter"/>
<security:intercept-url pattern="/*.html*" access="ROLE_ANONYMOUS,admin,user" />
<security:intercept-url pattern="/*.jsp" access="ROLE_ANONYMOUS,admin,user" />
<security:intercept-url pattern="/**/*.html**" access="ROLE_ANONYMOUS,user,admin" />
</security:http>
<bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter">
<property name="channelDecisionManager" ref="channelDecisionManager"/>
<property name="securityMetadataSource">
<security:filter-security-metadata-source path-type="ant">
<security:intercept-url pattern="/*.jsp**" access="REQUIRES_SECURE_CHANNEL" />
<security:intercept-url pattern="/**/*.html**" access="REQUIRES_SECURE_CHANNEL" />
</security:filter-security-metadata-source>
</property>
</bean>
<bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
<property name="channelProcessors">
<list>
<ref bean="secureProcessor"/>
<ref bean="insecureProcessor"/>
</list>
</property>
</bean>
<bean id="secureProcessor" class="org.springframework.security.web.access.channel.SecureChannelProcessor" >
<property name="entryPoint" ref="retryWithHttps"/>
</bean>
<bean id="insecureProcessor" class="org.springframework.security.web.access.channel.InsecureChannelProcessor">
<property name="entryPoint" ref="retryWithHttp"/>
</bean>
<bean id="retryWithHttps" class="com.my.webapp.filter.RetryWithHttpsEntryPoint" />
<bean id="retryWithHttp" class="com.my.webapp.filter.RetryWithHttpEntryPoint" />
</beans>
I found another way to achieve the same thing with much less code and complexity. You can simply use a BeanPostProcessor to get the SecureChannelProcessor and InsecureChannelProcessor and then set your own entry point on them. That way, you can still use the defaults on everything else.
The BeanPostProcessor:
#Component
public class ChannelProcessorsPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
if (bean instanceof SecureChannelProcessor) ((SecureChannelProcessor)bean).setEntryPoint(new MyEntryRetryPoint("https://", 443));
else if (bean instanceof InsecureChannelProcessor) ((InsecureChannelProcessor)bean).setEntryPoint(new MyEntryRetryPoint("http://", 80));
return bean;
}
#Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException {
return bean;
}
}
I think it's better to write a redirect strategy:
#Component
public class PermanentRedirectStrategy implements RedirectStrategy {
private boolean contextRelative;
#Override
public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
response.setHeader("Location", response.encodeRedirectURL(calculateRedirectUrl(request.getContextPath(), url)));
}
/**
* Unfortunately DefaultRedirectStrategy.calculateRedirectUrl is private
* If this weren't the case, we could extend this class from DefaultRedirectStrategy
* to use its method directly without copying it
*/
private String calculateRedirectUrl(String contextPath, String url) {
if (!UrlUtils.isAbsoluteUrl(url)) {
if (contextRelative) {
return url;
} else {
return contextPath + url;
}
}
// Full URL, including http(s)://
if (!contextRelative) {
return url;
}
// Calculate the relative URL from the fully qualified URL, minus the last
// occurence of the scheme and base context
url = url.substring(url.lastIndexOf("://") + 3); // strip off scheme
url = url.substring(url.indexOf(contextPath) + contextPath.length());
if (url.length() > 1 && url.charAt(0) == '/') {
url = url.substring(1);
}
return url;
}
}
and then setting it to the existing entry point:
#Component
public class ChannelProcessorsPostProcessor implements BeanPostProcessor {
#Autowired
private RedirectStrategy permanentRedirectStrategy;
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
ChannelEntryPoint entryPoint = null;
if (bean instanceof SecureChannelProcessor) {
entryPoint = ((SecureChannelProcessor) bean).getEntryPoint();
} else if (bean instanceof InsecureChannelProcessor) {
entryPoint = ((InsecureChannelProcessor) bean).getEntryPoint();
}
if (entryPoint != null && AbstractRetryEntryPoint.class.isAssignableFrom(entryPoint.getClass())) {
((AbstractRetryEntryPoint) entryPoint).setRedirectStrategy(permanentRedirectStrategy);
}
return bean;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}