Explanation of our project security: 

Clippitamine security uses Apache Shiro as underlaying security library.
Shiro was used, because it allowed both simple and robust security
requirements.
Clippitamine-Shiro security filter implementations:


cTokenAuthzFilter
Filter checking (in isAccessAllowed() method) if JWT token exists in
Authorization: request header and checks if token is valid.

cRestAuthFilter
Resolves request body for login request details and executes login.

cFormAuthFilter
Resolves login request details from application/x-www-form-urlencoded
parameters and executes login.

cHeaderAuthFilter
Resolved login request details from request headers and executes login.

cTokenAuthFilter
Resolves login request details from request parameter and executes login

cLoginRequiredFilter
Allways redirects to loginRequiredUri

Shiro security filter implementation properties:


How they work together:
*AuthFilter implementations only serve for login purposes. Meaning that
filter chain is executed until one of the filters succedes with login user.
If none of the filters resolves login request, last filter
cLoginRequiredFilter is executed and it always redirects to login required
end point.
*AuthzFilter implementations are used for general checking if user is
allowed or denied access. For exmaple, checks validity of JWT token.

REST API security:
- with JWT

filter chain on endpoint /**

noSessionCreation
cTokenTAuthzFilter
cRestAuthFilter
cFormAuthFilter
cTokenAuthFilter
cLoginRequiredFilter

filter chain on endpoint /loginRequired

anon
noSessionCreation


filter chain on endpoint /invalidCredentials

anon
noSessionCreation



MEDIA API security:
without JWT

endpoinds secured by filters:

cTokenAuthFilter
dcLoginRequiredFilter

----------------------------------------------------------------------------

Here is full code (mind our classes and package names .. change what you
need .. here is the code that works)

StringToken code 

package com.clippitamine.security.core.tokens;

import org.apache.shiro.authc.AuthenticationToken;

public class StringTokenAuthenticationToken implements AuthenticationToken {


  private String token    = null;
  private String ip         = null;


  public StringTokenAuthenticationToken(final String theTheToken, final
String theIp) {
    token    = theTheToken;
    ip        = theIp;
  }

  public String getIp() {
    return ip;
  }

  public void setIp(String ip) {
    this.ip = ip;
  }

  public boolean hasIp() {
    return this.ip != null && !this.ip.isEmpty();
  }

  @Override
  public Object getCredentials() {
    return this.token;
  }

  @Override
  public Object getPrincipal() {
    return this.token;
  }
}

Base auth filter
package com.clippitamine.security.core.filters;

import lombok.Data;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;


@Data
abstract public class BaseAuthFilter extends AuthenticatingFilter {


    final Logger log;

    /**
     * URL on failed login request
     **/
    @Value("#{ @environment['clippitamine.security.loginFailUrl'] ?:
\"/invalidCredentials\"}")
    String failUrl;

    /**
     * URL on not logged in request
     **/
    @Value("#{ @environment['clippitamine.security.loginInfoUrl'] ?:
\"/loginInfo\"}")
    String loginInfoUrl;

    /**
     * username param name
     */
    @Value("#{ @environment['clippitamine.security.usernameParam'] ?:
\"username\"}")
    String usernameParam;

    /**
     * username param name
     */
    @Value("#{ @environment['clippitamine.security.passwordParam'] ?:
\"password\"}")
    String passwordParam;


    public BaseAuthFilter()
    {
        this.log = LoggerFactory.getLogger(RestAuthFilter.class);
    }


    protected boolean executeLogin(AuthenticationToken token, ServletRequest
request, ServletResponse response) throws Exception {
        if (token == null) {
            String msg = "createToken method implementation returned null. A
valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        this.log.info("Executing login for [{}]", token.getPrincipal());
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            this.log.info("Login success for [{}]", token.getPrincipal());
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            this.log.error("Cannot login user [{}] ", token.getPrincipal(),
e);
            return onLoginFailure(token, e, request, response);
        }
    }


    @Override
    protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request, ServletResponse response)
{
        try {
            WebUtils.issueRedirect(request, response, this.failUrl);
        } catch (IOException ex) {
            this.log.error("Error while redirecting to [{}]", this.failUrl,
ex);
        }
        return false;
    }


    abstract protected AuthenticationToken createToken(ServletRequest
request, ServletResponse response);
}


Token filter 

import com.clippitamine.base.utils.QueryStringParser;
import com.clippitamine.security.core.tokens.StringTokenAuthenticationToken;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;


public class TokenAuthenticationFilter extends BaseAuthFilter {


  private static final Logger LOG =
LoggerFactory.getLogger(TokenAuthenticationFilter.class);

  @Override
  protected AuthenticationToken createToken(ServletRequest request,
ServletResponse response) {
    HttpServletRequest httpRequest = WebUtils.toHttp(request);
    String tokenId = null;
    try {
      tokenId = QueryStringParser.get(httpRequest.getQueryString(),
"tokenParamName");
    } catch (Exception e) {
      LOG.error("Unable to parse [dcStringToken]", e);
      return null;
    }
    //is client behind something?
    String ipAddress = httpRequest.getHeader("X-FORWARDED-FOR");
    if (ipAddress == null) {
      ipAddress = request.getRemoteAddr();
    }
    return new StringTokenAuthenticationToken(tokenId, ipAddress);
  }

  @Override
  protected boolean onAccessDenied(ServletRequest request, ServletResponse
response) throws Exception {
    AuthenticationToken authenticationToken = this.createToken(request,
response);
    if (authenticationToken != null) {
      return this.executeLogin(authenticationToken, request, response);
    }
    return true;
  }
}

Token realm (please note that this is spring-boot-hibernate implementation 


package com.clippitamine.security.hibernate.realms.auth;

/*
 * Apache License, Version 2.0
 *
 * Copyright (c) 2011, Dropchop
 *
 * 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.
 */

import com.clippitamine.base.utils.NetUtils;
import com.clippitamine.entities.core.security.Principal;
import com.clippitamine.entities.core.security.Token;
import com.clippitamine.repositories.db.security.PrincipalRepository;
import com.clippitamine.repositories.db.security.TokenRepository;
import com.clippitamine.security.core.tokens.StringTokenAuthenticationToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.springframework.stereotype.Component;

import java.net.InetAddress;
import java.time.ZonedDateTime;
import java.util.*;

@Component
public class TokenRealm extends UsernamePasswordRealm {

    private TokenRepository tokenRepository;

    public TokenRealm(
            final TokenRepository theTokenRepository,
            final PrincipalRepository thePrincipalRepository,
            final CredentialsMatcher theCredentialsMatcher) {
        super(thePrincipalRepository, theCredentialsMatcher);
        this.tokenRepository = theTokenRepository;
       
this.setAuthenticationTokenClass(StringTokenAuthenticationToken.class);
    }


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(final
AuthenticationToken theToken) {
        StringTokenAuthenticationToken token =
(StringTokenAuthenticationToken) theToken;


        ZonedDateTime now = ZonedDateTime.now();
        Principal user;
        Token userToken =
this.tokenRepository.findById(UUID.fromString((String)
token.getPrincipal())).orElse(null);
        if (userToken == null) {
            throw new AuthenticationException("User token [" +
token.getPrincipal() + "] not found!");
        }
      /*if (!userToken.getName().equals(Token.TokenName.LOGIN.name())) {
        throw new AuthenticationException("Invalid token [" +
token.getPrincipal() + "] name [" + userToken.getName() + "] should be [" +
Token.TokenName.LOGIN.name() + "]!");
      }*/
        if (now.isAfter(userToken.getExpiryDate())) {
            throw new AuthenticationException("User token [" +
token.getPrincipal() + "] expired!");
        }

        if (token.hasIp()) {
            //check IP addresses
            boolean validIp = false;
            List<InetAddress> allowedIps = new LinkedList<>();
            String netMask = userToken.getNetMask();
            if (netMask != null && !netMask.isEmpty()) {
                String[] netIps = netMask.split("\\n");
                for (String ip : netIps) {
                    try {
                        if (NetUtils.validateIP(token.getIp(), ip)) {
                            validIp = true;
                            break;
                        }
                    } catch (Exception e) {
                        this.log.error("Exception when checking user ip with
token IPs", e);
                    }
                }
                if (!validIp) {
                    throw new AuthenticationException("User ip [" +
token.getIp() + "] is invalid for token [" + token.getPrincipal() + "]");
                }
            }
        }

        user = userToken.getPrincipal();
        if (user == null) {
            throw new AuthenticationException("User with token [" +
token.getPrincipal() + "] not resolved!");
        }
        if (!user.getActive()) {
            throw new AuthenticationException("User with token [" +
token.getPrincipal() + "] not active!");
        }
        this.log.info("Found user [{}] with username [{}] from token [{}]",
user.getUuid(), token.getPrincipal(), token.getPrincipal());
        SimpleAuthenticationInfo authenticationInfo = new
SimpleAuthenticationInfo(user.getUuid(), token.getPrincipal(),
this.getName());
        this.clearCache(authenticationInfo.getPrincipals());
        return authenticationInfo;
    }

}

Regards 
Armando 



--
Sent from: http://shiro-user.582556.n2.nabble.com/

Reply via email to