I am using Spring security and I have a custom Authorization Filter without explicit URL mapping. I have added my Authorization filter after UsernamePasswordAuthenticationFilter.class.
The problem with this approach is, even for the URL patterns which are either permitted or permitted with authentication are going through my Authorization Filter. I do not want to explicitly configure my filter mapping on Authorization Filter since I feel that is redundant(already present in WebSecurityConfigurerAdapter). Is there anyway I can get access to Spring Security metadata or any other way to skip the Authorization Filter for the URLs which are marked as permitAll() or authenticated()?
public class AuthorizationFilter implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
//Calls a webservice to fetch the authorities based on the URL pattern.
//Adds the authentication principal with Authorities to SecurityContextHolder.
}
public void init(FilterConfig config) throws ServletException {
}
}
I have solved this by extending WebExpressionVoter as below:
public class CustomAuthoritiesVoter extends WebExpressionVoter {
private static final String SUPPORTED_CLASS = "org.springframework.security.web.access.expression.WebExpressionConfigAttribute";
#Override
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) {
String exp = getExpressionString(attributes);
if (null != exp && !StringUtils.equalsAny(exp, "authenticated", "permitAll")) {
//Call service to fetch the authorities based on the userID and URL
//Set Authentication principal along with fetched Authorities using SecurityContextHolder
}
return super.vote(authentication, fi, attributes);
}
private String getExpressionString(Collection<ConfigAttribute> attributes) {
try {
for (ConfigAttribute attribute : attributes) {
if (attribute.getClass().isAssignableFrom(Class.forName(SUPPORTED_CLASS))) {
return attribute.toString();
}
}
} catch (ClassNotFoundException e) {
log.warn(e.getMessage(), e);
}
return null;
}
}
Related
I need to create a gated content setup in aem. So basically, the page ( which contains some assets) should only be reachable after the user has submitted a form. And the assets should also be not accessible directly without submitting the form. So I have created a servlet filter to achieve this, the page logic works fine but when I access assets directly the request is not reaching the servlet filters. The below is my code please let me know if there is some mistake or there is any other preferred method, I have also tried setting this ServiceRanking(1)
#Component(service = Filter.class,
property = {
EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
})
#ServiceRankin(-700)
public class GatedContentFilter implements Filter {
#Override
public void doFilter(final ServletRequest request, final ServletResponse response,
final FilterChain filterChain) throws IOException, ServletException {
final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response;
final Resource resource = slingRequest.getResource();
if(resource.getPath().startsWith("/content/abc")) {
//page logic
}
else if (resource.getPath().startsWith("/content/dam/abc/gated-assets")) {
//assets logic
}
filterChain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
I am having situation like: in the preHandle() method of the class implementing HandlerInterceptor, i am having sessionId getting in the incoming HttpServletRequest object request. now using this session id i am fetching userInfo from the DB. the same info i have to use somewhere else like service layer to process the request.
It would be very helpful if anyone of you help me out to achieve it. Thanks in advance.
You can use a ThreadLocal to store a reference to a user that will only be accessible to the current thread of execution.
https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html
You can wrap this in a context class so that in your service you can access the current user via the static call: User user = UserContextUtils.getUser();
UserContextUtils:
public class UserContextUtils {
private static final ThreadLocal<User> CONTEXT = new ThreadLocal<>();
public static void setUser(User user) {
CONTEXT.set(user);
}
public static User getUser() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
The Interceptor:
public class MyHandlerInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
User user = null;// get user from the database.
UserContextUtils.setUser(user);
return true;
}
#Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler,
Exception ex) {
// as some web servers re-use threads, you must ensure that the
// context is cleared on completion either here or elsewhere.
UserContextUtils.clear();
}
#Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
}
My configuration of Spring Security is
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**"); // #3
}
Taken from here.
The documentation for ignorig says
Allows adding RequestMatcher instances that should that Spring Security should ignore. ... Typically the requests that are registered should be that of only static resources.
I would like to add some headers to files served from resources.
E.g.: Strict-Transport-Security: max-age=31536000, X-Content-Type-Options: nosniff.
How I can do it?
One solution it to change it to
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/resources/**").permitAll()
.and()
.antMatcher("/resources/**").headers().cacheControl()
}
Example how to allow cache control headers PLUS ALL DEFAULT SPRING SECURITY HEADERS.
I have struggled with the same problem. When I ignore specific requests in WebSecurity, the headers were gone.
I fixed the missing headers, by applying a filter on each request that adds my headers.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(securityHeaderFilter, BasicAuthenticationFilter.class)
...
}
The filter code looks like this. The important thing to note here, is that the Filter must be declared as a #Component. When you miss the #Component annotation, the filter will be ignored.
#Component
public class SecurityHeaderFilter implements Filter {
#Override
public void init(FilterConfig fc) throws ServletException {
// Do nothing
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
httpServletResponse.setHeader(
"custom-header1", "header-value1");
httpServletResponse.setHeader(
"custom-header2", "header-value2");
chain.doFilter(request, response);
}
#Override
public void destroy() {
// Do nothing
}
}
I have used the following solution:
#Bean
public FilterRegistrationBean setHeaders() {
HstsHeaderWriter hstsHeaderWriter = new HstsHeaderWriter(31536000, true);
XContentTypeOptionsHeaderWriter xContentTypeOptionsHeaderWriter = new XContentTypeOptionsHeaderWriter();
List<HeaderWriter> headerWriters = new ArrayList<>();
headerWriters.add(hstsHeaderWriter);
headerWriters.add(xContentTypeOptionsHeaderWriter);
HeaderWriterFilter headerWriterFilter = new HeaderWriterFilter(headerWriters);
FilterRegistrationBean bean = new FilterRegistrationBean(headerWriterFilter);
bean.setOrder(1);
return bean;
}
The above bean will add a filter globally on all the resources(even the ignoring ones). You can checkout the various implementations of org.springframework.security.web.header.HeaderWriter.java for the different kinds of security headers and add them all to HeaderWriterFilter.java.
AFAIK, the httpsessionlisterner implementation listener class is get instantiated when the first session is created.
Therefore, i would like access this instance because i need to count how many active session and display it some where and i would like to check which user is currently login. In the code below, there is list instance variable, i need to access this listener class in order to access the private variable.
#WebListener()
public class SessionListener implements HttpSessionListener, HttpSessionAttributeListener {
private List<HttpSession> sessionList;
public SessionListener() {
sessionList = new ArrayList<HttpSession>();
}
#Override
public void sessionCreated(HttpSessionEvent se) {
sessionList.add(se.getSession());
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
sessionList.remove(se.getSession());
}
#Override
public void attributeAdded(HttpSessionBindingEvent event) {
}
#Override
public void attributeRemoved(HttpSessionBindingEvent event) {
}
#Override
public void attributeReplaced(HttpSessionBindingEvent event) {
}
/**
* #return the sessionList
*/
public List<HttpSession> getSessionList() {
return Collections.unmodifiableList(sessionList);
}
Please help.
Thanks.
I have to make a few assumptions as you don't say how your authentication method works.
I will assume that your username will be contained in your HttpServletRequest (this is very common). Unless you have specifically coded your session to contain the username it will not contain the username of the authenticated user - the username is usually confined to the HttpServletRequest. Therefore you will not usually achieve your goal by using an HttpSessionListener. You probably know this but there are various "scopes".
application scope (ServletContext) - per application
session scope (HttpSession) - per session
request scope (HttpServletRequest) - per request
As I said, the username is usually stored in the request scope. You can access the session and application scopes from the request scope. You cannot access the request scope from the session scope (as this doesn't make sense!).
To solve your problem I would create a Map stored in the application scope and use a ServletFilter to populate it. You might want to use a time based cache (using the session time-out value) rather than a straight map as mostly sessions are started but timeout rather than get explicitly terminated by the user. kitty-cache is a really simple time based cache that you could use for this purpose.
Anyway a code sketch (untested) might look something like this:
public class AuthSessionCounter implements Filter {
private static final String AUTHSESSIONS = "authsessions";
private static ServletContext sc;
public void init(FilterConfig filterConfig) throws ServletException {
sc = filterConfig.getServletContext();
HashMap<String, String> authsessions = new HashMap<String, String>();
sc.setAttribute(AUTHSESSIONS, authsessions);
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest hsr = (HttpServletRequest) request;
if (hsr.getRemoteUser() != null) {
HttpSession session = hsr.getSession();
HashMap<String, String> authsessions = (HashMap<String, String>) sc.getAttribute(AUTHSESSIONS);
if (!authsessions.containsKey(session.getId())) {
authsessions.put(session.getId(), hsr.getRemoteUser());
sc.setAttribute(AUTHSESSIONS, authsessions);
}
}
chain.doFilter(request, response);
}
public void destroy(){}
}
You should now be able to obtain details of who and how many users are logged in from the authsessions Map that is stored in the application scope.
I hope this helps,
Mark
UPDATE
My authentication is works by checking
the username and password in servlet
and create a new session for it.
In which case a HttpSessionListener might work for you - although as I mentioned before you probably still need to use a time based cache due to the way that most user sessions timeout rather than terminate. My untested code sketch would now look something like this:
public class SessionCounter
implements HttpSessionListener, HttpSessionAttributeListener {
private static final String AUTHSESSIONS = "authsessions";
private static final String USERNAME = "username";
private static ServletContext sc;
public void sessionCreated(HttpSessionEvent se) {
if (sc == null) {
sc = se.getSession().getServletContext();
HashMap<String, String> authsessions = new HashMap<String, String>();
sc.setAttribute(AUTHSESSIONS, authsessions);
}
}
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
HashMap<String, String> authsessions =
(HashMap<String, String>) sc.getAttribute(AUTHSESSIONS);
authsessions.remove(session.getId());
sc.setAttribute(AUTHSESSIONS, authsessions);
}
public void attributeAdded(HttpSessionBindingEvent se) {
if (USERNAME.equals(se.getName())) {
HttpSession session = se.getSession();
HashMap<String, String> authsessions =
(HashMap<String, String>) sc.getAttribute(AUTHSESSIONS);
authsessions.put(session.getId(), (String) se.getValue());
sc.setAttribute(AUTHSESSIONS, authsessions);
}
}
public void attributeRemoved(HttpSessionBindingEvent se) {}
public void attributeReplaced(HttpSessionBindingEvent se) {}
}
My controller method is returning a ModelAndView, but there is also a requirement to write a cookie back to client. Is it possible to do it in Spring? Thanks.
If you add the response as parameter to your handler method (see flexible signatures of #RequestMapping annotated methods – same section for 3.2.x, 4.0.x, 4.1.x, 4.3.x, 5.x.x), you may add the cookie to the response directly:
Kotlin
#RequestMapping(["/example"])
fun exampleHandler(response: HttpServletResponse): ModelAndView {
response.addCookie(Cookie("COOKIENAME", "The cookie's value"))
return ModelAndView("viewname")
}
Java
#RequestMapping("/example")
private ModelAndView exampleHandler(HttpServletResponse response) {
response.addCookie(new Cookie("COOKIENAME", "The cookie's value"));
return new ModelAndView("viewname");
}
Not as part of the ModelAndView, no, but you can add the cookie directly to the HttpServletResponse object that's passed in to your controller method.
You can write a HandlerInterceptor that will take all Cookie instances from your model and generate the appropriate cookie headers. This way you can keep your controllers clean and free from HttpServletResponse.
#Component
public class ModelCookieInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception {
if (modelAndView != null) {
for (Object value : modelAndView.getModel().values()) {
if (value instanceof Cookie)
res.addCookie((Cookie) value);
}
}
}
}
NB . Don't forget to register the interceptor either with <mvc:interceptors> (XML config) or WebMvcConfigurer.addInterceptors() (Java config).
RustyX's solution in Java 8:
#Component
public class ModelCookieInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(HttpServletRequest req, HttpServletResponse res, Object handler, ModelAndView modelAndView) throws Exception{
if (modelAndView != null) {
modelAndView.getModel().values().stream()
.filter(c -> c instanceof Cookie)
.map(c -> (Cookie) c)
.forEach(res::addCookie);
}
}
}