Thanks a million for your reply! Lots of information for me to take this a step further.
/Bengt Den tors 22 nov. 2018 kl 09:22 skrev armandoxxx <[email protected]>: > 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/ >
