├── src
├── main
│ ├── webapp
│ │ ├── simpleshirosecuredapplication
│ │ │ ├── index.jsp
│ │ │ ├── account
│ │ │ │ ├── accessdenied.jsp
│ │ │ │ ├── logout.jsp
│ │ │ │ ├── allapplicationfunctions.jsp
│ │ │ │ ├── viewallaccounts.jsp
│ │ │ │ ├── login.jsp
│ │ │ │ └── personalaccountpage.jsp
│ │ │ ├── common
│ │ │ │ └── commonformstuff.jsp
│ │ │ ├── sales
│ │ │ │ └── sales.jsp
│ │ │ ├── scientists
│ │ │ │ └── scientists.jsp
│ │ │ ├── repairmen
│ │ │ │ └── repairmen.jsp
│ │ │ └── adminarea
│ │ │ │ └── administratorspage.jsp
│ │ ├── WEB-INF
│ │ │ ├── jetty-web.xml
│ │ │ ├── classes
│ │ │ │ └── META-INF
│ │ │ │ │ └── persistence.xml
│ │ │ └── web.xml
│ │ └── index.jsp
│ ├── resources
│ │ ├── truststore
│ │ ├── log4j.properties
│ │ ├── Shiro.ini
│ │ ├── antisamy-tinymce-1.4.4.xml
│ │ └── db.changelog.xml
│ └── java
│ │ └── org
│ │ └── meri
│ │ └── simpleshirosecuredapplication
│ │ ├── servlet
│ │ ├── ServletConstants.java
│ │ ├── VerboseFormAuthenticationFilter.java
│ │ ├── AbstractFieldsHandlingServlet.java
│ │ ├── CertificateOrFormAuthenticationFilter.java
│ │ ├── PerformFunctionAndGoBackServlet.java
│ │ └── AccountPageServlet.java
│ │ ├── authc
│ │ ├── X509CertificateAuthenticationToken.java
│ │ ├── X509CertificateOnlyAuthenticationToken.java
│ │ ├── X509CertificateUsernamePasswordToken.java
│ │ └── PrimaryPrincipalSameAuthenticationStrategy.java
│ │ ├── model
│ │ ├── UserPersonalData.java
│ │ └── ModelProvider.java
│ │ ├── sanitizer
│ │ └── Sanitizer.java
│ │ ├── actions
│ │ └── Actions.java
│ │ └── realm
│ │ ├── JNDIAndSaltAwareJdbcRealm.java
│ │ └── X509CertificateRealm.java
└── test
│ ├── resources
│ ├── keystore
│ └── clients
│ │ ├── physicien
│ │ ├── physicien.cer
│ │ ├── physicien.jks
│ │ └── physicien.p12
│ │ ├── productsales
│ │ ├── productsales.cer
│ │ ├── productsales.jks
│ │ └── productsales.p12
│ │ ├── administrator
│ │ ├── administrator.cer
│ │ ├── administrator.jks
│ │ └── administrator.p12
│ │ ├── mathematician
│ │ ├── mathematician.cer
│ │ ├── mathematician.jks
│ │ └── mathematician.p12
│ │ ├── servicessales
│ │ ├── servicessales.cer
│ │ ├── servicessales.jks
│ │ └── servicessales.p12
│ │ ├── friendlyrepaiman
│ │ ├── friendlyrepairman.cer
│ │ ├── friendlyrepairman.jks
│ │ └── friendlyrepairman.p12
│ │ └── unfriendlyrepaiman
│ │ ├── unfriendlyrepairman.cer
│ │ ├── unfriendlyrepairman.jks
│ │ └── unfriendlyrepairman.p12
│ └── java
│ └── org
│ └── meri
│ └── simpleshirosecuredapplication
│ ├── RunWaitTest.java
│ ├── util
│ ├── HashPassword.java
│ └── AnalyzeKeystore.java
│ └── test
│ └── AbstractContainerTest.java
├── target
└── test-classes
│ └── keystore
├── .classpath
├── .project
└── pom.xml
/src/main/webapp/simpleshirosecuredapplication/index.jsp:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/test/resources/keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/keystore
--------------------------------------------------------------------------------
/src/main/resources/truststore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/main/resources/truststore
--------------------------------------------------------------------------------
/target/test-classes/keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/target/test-classes/keystore
--------------------------------------------------------------------------------
/src/test/resources/clients/physicien/physicien.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/physicien/physicien.cer
--------------------------------------------------------------------------------
/src/test/resources/clients/physicien/physicien.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/physicien/physicien.jks
--------------------------------------------------------------------------------
/src/test/resources/clients/physicien/physicien.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/physicien/physicien.p12
--------------------------------------------------------------------------------
/src/test/resources/clients/productsales/productsales.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/productsales/productsales.cer
--------------------------------------------------------------------------------
/src/test/resources/clients/productsales/productsales.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/productsales/productsales.jks
--------------------------------------------------------------------------------
/src/test/resources/clients/productsales/productsales.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/productsales/productsales.p12
--------------------------------------------------------------------------------
/src/test/resources/clients/administrator/administrator.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/administrator/administrator.cer
--------------------------------------------------------------------------------
/src/test/resources/clients/administrator/administrator.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/administrator/administrator.jks
--------------------------------------------------------------------------------
/src/test/resources/clients/administrator/administrator.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/administrator/administrator.p12
--------------------------------------------------------------------------------
/src/test/resources/clients/mathematician/mathematician.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/mathematician/mathematician.cer
--------------------------------------------------------------------------------
/src/test/resources/clients/mathematician/mathematician.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/mathematician/mathematician.jks
--------------------------------------------------------------------------------
/src/test/resources/clients/mathematician/mathematician.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/mathematician/mathematician.p12
--------------------------------------------------------------------------------
/src/test/resources/clients/servicessales/servicessales.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/servicessales/servicessales.cer
--------------------------------------------------------------------------------
/src/test/resources/clients/servicessales/servicessales.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/servicessales/servicessales.jks
--------------------------------------------------------------------------------
/src/test/resources/clients/servicessales/servicessales.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/servicessales/servicessales.p12
--------------------------------------------------------------------------------
/src/test/resources/clients/friendlyrepaiman/friendlyrepairman.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/friendlyrepaiman/friendlyrepairman.cer
--------------------------------------------------------------------------------
/src/test/resources/clients/friendlyrepaiman/friendlyrepairman.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/friendlyrepaiman/friendlyrepairman.jks
--------------------------------------------------------------------------------
/src/test/resources/clients/friendlyrepaiman/friendlyrepairman.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/friendlyrepaiman/friendlyrepairman.p12
--------------------------------------------------------------------------------
/src/test/resources/clients/unfriendlyrepaiman/unfriendlyrepairman.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/unfriendlyrepaiman/unfriendlyrepairman.cer
--------------------------------------------------------------------------------
/src/test/resources/clients/unfriendlyrepaiman/unfriendlyrepairman.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/unfriendlyrepaiman/unfriendlyrepairman.jks
--------------------------------------------------------------------------------
/src/test/resources/clients/unfriendlyrepaiman/unfriendlyrepairman.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SomMeri/SimpleShiroSecuredApplication/HEAD/src/test/resources/clients/unfriendlyrepaiman/unfriendlyrepairman.p12
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/servlet/ServletConstants.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.servlet;
2 |
3 | public class ServletConstants {
4 |
5 | public static final String originalPage = "originalPage";
6 | public static final String actionResultMessage = "actionResultMessage";
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/authc/X509CertificateAuthenticationToken.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.authc;
2 |
3 | import java.security.cert.X509Certificate;
4 |
5 | import org.apache.shiro.authc.AuthenticationToken;
6 |
7 | public interface X509CertificateAuthenticationToken extends AuthenticationToken {
8 |
9 | public abstract X509Certificate getCertificate();
10 |
11 | }
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/account/accessdenied.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | Access Denied
8 |
9 |
10 | <%@include file="/simpleshirosecuredapplication/common/commonformstuff.jsp" %>
11 | Sorry, you do not have access rights to that area.
12 |
13 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/common/commonformstuff.jsp:
--------------------------------------------------------------------------------
1 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
2 | ">Go Home
3 | ">logout
4 | <%
5 | String reqUrl = request.getServletPath().toString();
6 | String actionMessage = (String) request.getAttribute("actionResultMessage");
7 | if (actionMessage!=null) {
8 | %>
9 | <%=actionMessage %>
10 | <%} %>
11 |
12 |
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/servlet/VerboseFormAuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.servlet;
2 |
3 | import javax.servlet.ServletRequest;
4 |
5 | import org.apache.shiro.authc.AuthenticationException;
6 | import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
7 |
8 | public class VerboseFormAuthenticationFilter extends FormAuthenticationFilter {
9 |
10 | @Override
11 | protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
12 | String message = ae.getMessage();
13 | request.setAttribute(getFailureKeyAttribute(), message);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/account/logout.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | Logout
8 |
9 |
10 | <%@include file="/simpleshirosecuredapplication/common/commonformstuff.jsp" %>
11 | <%@ page import="org.apache.shiro.SecurityUtils" %>
12 | <% SecurityUtils.getSubject().logout();%>
13 | You have successfully logged out.
14 |
15 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | SimpleShiroSecuredApplication
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.maven.ide.eclipse.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.maven.ide.eclipse.maven2Nature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/test/java/org/meri/simpleshirosecuredapplication/RunWaitTest.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication;
2 |
3 | import java.io.IOException;
4 | import java.net.MalformedURLException;
5 |
6 | import org.junit.Test;
7 | import org.meri.simpleshirosecuredapplication.test.AbstractContainerTest;
8 |
9 | import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
10 |
11 | public class RunWaitTest extends AbstractContainerTest {
12 |
13 | @Test
14 | public void sleepForever() throws FailingHttpStatusCodeException, MalformedURLException, IOException, InterruptedException {
15 | while (true)
16 | Thread.sleep(9999);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/jetty-web.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | jdbc/SimpleShiroSecuredApplicationDB
7 |
8 |
9 | ../SimpleShiroSecuredApplicationDatabase
10 | create
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/classes/META-INF/persistence.xml:
--------------------------------------------------------------------------------
1 |
3 |
5 | org.apache.openjpa.persistence.PersistenceProviderImpl
6 |
7 | jdbc/SimpleShiroSecuredApplicationDB
8 |
9 | org.meri.simpleshirosecuredapplication.model.UserPersonalData
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/servlet/AbstractFieldsHandlingServlet.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.servlet;
2 |
3 | import javax.servlet.http.HttpServlet;
4 | import javax.servlet.http.HttpServletRequest;
5 |
6 | import org.meri.simpleshirosecuredapplication.sanitizer.Sanitizer;
7 |
8 | /**
9 | * Parent of all servlets reading user supplied data. Use {@link #getField(HttpServletRequest, String)}
10 | * to get fields values.
11 | *
12 | */
13 | @SuppressWarnings("serial")
14 | public abstract class AbstractFieldsHandlingServlet extends HttpServlet {
15 |
16 | private Sanitizer sanitizer = new Sanitizer();
17 |
18 | public AbstractFieldsHandlingServlet() {
19 | super();
20 | }
21 |
22 | /**
23 | * Reads sanitized request parameter value from request field.
24 | *
25 | * @param request http request
26 | * @param parameter name of request parameter
27 | *
28 | * @return request parameter value
29 | */
30 | protected String getField(HttpServletRequest request, String parameter) {
31 | String dirtyValue = request.getParameter(parameter);
32 | return sanitizer.sanitize(dirtyValue);
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/src/main/webapp/index.jsp:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
4 | Welcome
5 | Simple web application for trying out Shiro security framework. There is no security yet.
6 | ">login
7 | ">all application functions
8 | ">view all accounts
9 | ">personal account page
10 | ">administrators page
11 | ">repairmen page
12 | ">sales page
13 | ">scientists page
14 | ">logout
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/account/allapplicationfunctions.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | All Application Functions
8 |
9 |
10 | <%@page import="org.meri.simpleshirosecuredapplication.actions.Actions"%>
11 |
12 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/model/UserPersonalData.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.model;
2 |
3 | import javax.persistence.Column;
4 | import javax.persistence.Entity;
5 | import javax.persistence.Id;
6 | import javax.persistence.Table;
7 |
8 | @Entity
9 | @Table(name="user_personal_data")
10 | public class UserPersonalData {
11 |
12 | private String userName;
13 | private String firstname;
14 | private String lastname;
15 | private String about;
16 |
17 |
18 | @Column(name = "user_name")
19 | @Id
20 | public String getUserName() {
21 | return userName;
22 | }
23 |
24 | public void setUserName(String userName) {
25 | this.userName = userName;
26 | }
27 |
28 | @Column
29 | public String getFirstname() {
30 | return firstname;
31 | }
32 |
33 | public void setFirstname(String firstname) {
34 | this.firstname = firstname;
35 | }
36 |
37 | @Column
38 | public String getLastname() {
39 | return lastname;
40 | }
41 |
42 | public void setLastname(String lastname) {
43 | this.lastname = lastname;
44 | }
45 |
46 | @Column
47 | public String getAbout() {
48 | return about;
49 | }
50 |
51 | public void setAbout(String about) {
52 | this.about = about;
53 | }
54 |
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/account/viewallaccounts.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 | <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
4 |
5 |
6 |
7 |
8 |
9 | View All Accounts
10 |
11 |
12 | <%@include file="/simpleshirosecuredapplication/common/commonformstuff.jsp" %>
13 | <%@page import="org.apache.shiro.SecurityUtils"%>
14 | <%@page import="org.meri.simpleshirosecuredapplication.model.ModelProvider"%>
15 | <%@page import="org.meri.simpleshirosecuredapplication.model.UserPersonalData"%>
16 | <%@page import="java.util.List"%>
17 | All users with filled account data:
18 | <%
19 | ModelProvider mp = new ModelProvider();
20 | List usersData = mp.getAllUsersData();
21 | pageContext.setAttribute("allusers", usersData);
22 | mp.close();
23 | %>
24 |
25 | <%
26 | for (UserPersonalData data : usersData) {
27 | %>
28 | - <%=data.getFirstname()%> <%=data.getLastname()%>
29 |
30 |
31 | <% } %>
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/test/java/org/meri/simpleshirosecuredapplication/util/HashPassword.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.util;
2 |
3 | import org.apache.shiro.crypto.hash.Sha256Hash;
4 | import org.apache.shiro.util.SimpleByteSource;
5 |
6 | public class HashPassword {
7 |
8 | /**
9 | * @param args
10 | */
11 | public static void main(String[] args) {
12 | simpleHash("heslo");
13 | simpleSaltedHash("administrator", "heslo");
14 | simpleSaltedHash("friendlyrepairman", "heslo");
15 | simpleSaltedHash("unfriendlyrepairman", "heslo");
16 | simpleSaltedHash("mathematician", "heslo");
17 | simpleSaltedHash("physicien", "heslo");
18 | simpleSaltedHash("productsales", "heslo");
19 | simpleSaltedHash("servicessales", "heslo");
20 |
21 | }
22 |
23 | private static String simpleHash(String password) {
24 | Sha256Hash sha256Hash = new Sha256Hash(password);
25 | String result = sha256Hash.toHex();
26 |
27 | System.out.println("Simple hash: " + result);
28 | return result;
29 | }
30 |
31 | private static String simpleSaltedHash(String username, String password) {
32 | Sha256Hash sha256Hash = new Sha256Hash(password, (new SimpleByteSource("random_salt_value_" + username)).getBytes());
33 | String result = sha256Hash.toHex();
34 |
35 | System.out.println(username + " simple salted hash: " + result);
36 | return result;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/authc/X509CertificateOnlyAuthenticationToken.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.authc;
2 |
3 | import java.security.cert.X509Certificate;
4 | /**
5 | * Token for certificate based authentication. The certificate is used as both principal and credentials.
6 | *
7 | */
8 | @SuppressWarnings("serial")
9 | public class X509CertificateOnlyAuthenticationToken implements X509CertificateAuthenticationToken {
10 |
11 | //certificate is used as both credentials and principal
12 | private X509Certificate certificate;
13 |
14 | public X509CertificateOnlyAuthenticationToken() {
15 | super();
16 | }
17 |
18 | public X509CertificateOnlyAuthenticationToken(X509Certificate certificate) {
19 | super();
20 | this.certificate = certificate;
21 | }
22 |
23 | @Override
24 | public Object getPrincipal() {
25 | return getCertificate();
26 | }
27 |
28 | @Override
29 | public Object getCredentials() {
30 | return getCertificate();
31 | }
32 |
33 | /* (non-Javadoc)
34 | * @see org.meri.simpleshirosecuredapplication.authc.X509CertificateAuthenticationToken#getCertificate()
35 | */
36 | @Override
37 | public X509Certificate getCertificate() {
38 | return certificate;
39 | }
40 |
41 | public void setCertificate(X509Certificate certificate) {
42 | this.certificate = certificate;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/sanitizer/Sanitizer.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.sanitizer;
2 |
3 | import java.io.InputStream;
4 |
5 | import org.owasp.validator.html.AntiSamy;
6 | import org.owasp.validator.html.CleanResults;
7 | import org.owasp.validator.html.Policy;
8 | import org.owasp.validator.html.PolicyException;
9 | import org.owasp.validator.html.ScanException;
10 | /**
11 | * Sanitize user input into XSS attack safe string. The class is thread safe.
12 | */
13 | public class Sanitizer {
14 |
15 | private static final String POLICY_FILE_LOCATION = "/antisamy-tinymce-1.4.4.xml";
16 | private static Policy policy;
17 |
18 | public Sanitizer() {
19 | }
20 |
21 | public String sanitize(String dirtyInput) {
22 | AntiSamy as = new AntiSamy();
23 | try {
24 | CleanResults cr = as.scan(dirtyInput, getPolicy());
25 | return cr.getCleanHTML();
26 | } catch (ScanException e) {
27 | throw new RuntimeException(e);
28 | } catch (PolicyException e) {
29 | throw new RuntimeException(e);
30 | }
31 | }
32 |
33 | private Policy getPolicy() {
34 | if (policy==null) {
35 | try {
36 | InputStream resourceAsStream = getClass().getResourceAsStream(POLICY_FILE_LOCATION);
37 | policy = Policy.getInstance(resourceAsStream);
38 | } catch (PolicyException e) {
39 | throw new RuntimeException(e);
40 | }
41 | }
42 | return policy;
43 | }
44 |
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/account/login.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | Please Log In
8 |
9 |
10 | <%@include file="/simpleshirosecuredapplication/common/commonformstuff.jsp" %>
11 | <%
12 | String errorDescription = (String) request.getAttribute("simpleShiroApplicationLoginFailure");
13 | if (errorDescription!=null) {
14 | %>
15 | Login attempt was unsuccessful: <%=errorDescription%>
16 | <%
17 | }
18 | %>
36 |
37 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/authc/X509CertificateUsernamePasswordToken.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.authc;
2 |
3 | import java.security.cert.X509Certificate;
4 |
5 | import org.apache.shiro.authc.UsernamePasswordToken;
6 |
7 | @SuppressWarnings("serial")
8 | public class X509CertificateUsernamePasswordToken extends UsernamePasswordToken implements X509CertificateAuthenticationToken {
9 |
10 | private X509Certificate certificate;
11 |
12 | public X509CertificateUsernamePasswordToken() {
13 | super();
14 | }
15 |
16 | public X509CertificateUsernamePasswordToken(String username, String password) {
17 | super(username, password);
18 | }
19 |
20 | public X509CertificateUsernamePasswordToken(X509Certificate certificate) {
21 | super();
22 | this.certificate = certificate;
23 | }
24 |
25 | public X509CertificateUsernamePasswordToken(String username, String password, X509Certificate certificate) {
26 | super(username, password);
27 | this.certificate = certificate;
28 | }
29 |
30 | public X509CertificateUsernamePasswordToken(String username, String password, boolean rememberMe, String host, X509Certificate certificate) {
31 | super(username, password, rememberMe, host);
32 | this.certificate = certificate;
33 | }
34 |
35 | @Override
36 | public X509Certificate getCertificate() {
37 | return certificate;
38 | }
39 |
40 | public void setCertificate(X509Certificate certificate) {
41 | this.certificate = certificate;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/sales/sales.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | Sales Page
8 |
9 |
10 | <%@page import="org.meri.simpleshirosecuredapplication.actions.Actions"%>
11 |
12 |
39 |
40 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/scientists/scientists.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | Scientists Page
8 |
9 |
10 | <%@page import="org.meri.simpleshirosecuredapplication.actions.Actions"%>
11 |
12 |
39 |
40 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/repairmen/repairmen.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | Repairmen Page
8 |
9 |
10 | <%@page import="org.meri.simpleshirosecuredapplication.actions.Actions"%>
11 |
12 |
39 |
40 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/adminarea/administratorspage.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
7 | Administrator Page
8 |
9 |
10 | <%@page import="org.meri.simpleshirosecuredapplication.actions.Actions"%>
11 |
12 |
39 |
40 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/actions/Actions.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.actions;
2 |
3 | import org.apache.shiro.SecurityUtils;
4 | import org.apache.shiro.authz.UnauthorizedException;
5 |
6 | public enum Actions {
7 |
8 | MANAGE_REPAIRMEN("MANAGE_REPAIRMEN", "functions:manage:repairmen"), MANAGE_SALES("MANAGE_SALES", "functions:manage:sales"), MANAGE_SCIENTISTS("MANAGE_SCIENTISTS", "functions:manage:scientists"),
9 | REPAIR_REFRIGERATOR("REPAIR_REFRIGERATOR", "functions:repair:refrigerator"), REPAIR_FRIDGE("REPAIR_FRIDGE", "functions:repair:bridge"), REPAIR_DOOR("REPAIR_DOOR", "functions:repair:door"),
10 | SALE_PRODUCT("SALE_PRODUCT", "functions:sale:product"), COLLECT_BONUS("COLLECT_BONUS", "functions:sale:collectbonus"), MEET_CUSTOMER("MEET_CUSTOMER", "functions:sale:meetcustomer"),
11 | RESEARCH_NEW_STUFF("RESEARCH_NEW_STUFF", "functions:science:research"), WRITE_ARTICLE("WRITE_ARTICLE", "functions:science:writearticle"), PREPARE_TALK("PREPARE_TALK", "functions:science:preparetalk");
12 |
13 | public String doIt() {
14 | String neededPermission = getNeededPermission();
15 | if (SecurityUtils.getSubject().isPermitted(neededPermission))
16 | return "Function " + getName() + " run succesfully.";
17 |
18 | throw new UnauthorizedException("Logged user does not have " + neededPermission + " permission");
19 | }
20 |
21 | private String getNeededPermission() {
22 | return permission;
23 | }
24 |
25 | public String getName() {
26 | return name;
27 | }
28 |
29 | private Actions(String name, String permission) {
30 | this.name = name;
31 | this.permission = permission;
32 | }
33 |
34 | private final String name;
35 | private final String permission;
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/org/meri/simpleshirosecuredapplication/util/AnalyzeKeystore.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.util;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.IOException;
5 | import java.math.BigInteger;
6 | import java.security.KeyStore;
7 | import java.security.cert.X509Certificate;
8 | import java.util.Enumeration;
9 |
10 | import org.apache.xerces.impl.dv.util.Base64;
11 |
12 | /**
13 | * Utility class. Investigates keystore file and prints all certificates along with authority names and serial numbers.
14 | */
15 | public class AnalyzeKeystore {
16 |
17 | public static void main(String[] args) {
18 | AnalyzeKeystore instance = new AnalyzeKeystore();
19 | instance.certificateOK("src/main/resources/truststore", "secret");
20 | }
21 |
22 | private void certificateOK(String truststore, String password) {
23 | FileInputStream stream = null;
24 |
25 | try {
26 | stream = new FileInputStream(truststore);
27 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
28 | keyStore.load(stream, password.toCharArray());
29 |
30 | Enumeration aliases = keyStore.aliases();
31 | while (aliases.hasMoreElements()) {
32 | String alias = aliases.nextElement();
33 | X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
34 |
35 | BigInteger serialNumber = certificate.getSerialNumber();
36 | String issuerName = certificate.getIssuerDN().getName();
37 | String base64Serial = Base64.encode(serialNumber.toByteArray());
38 |
39 | System.out.println(alias + ": " + base64Serial + "|XX|" + issuerName);
40 | }
41 |
42 | } catch (Exception ex) {
43 | ex.printStackTrace();
44 | } finally {
45 | try {
46 | stream.close();
47 | } catch (IOException e) {
48 | }
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/webapp/simpleshirosecuredapplication/account/personalaccountpage.jsp:
--------------------------------------------------------------------------------
1 | <%@ page language="java" contentType="text/html; charset=ISO-8859-1"
2 | pageEncoding="ISO-8859-1"%>
3 |
4 |
5 |
6 |
13 |
14 | Personal Account
15 |
16 |
17 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/model/ModelProvider.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.model;
2 |
3 | import java.util.List;
4 |
5 | import javax.persistence.EntityManager;
6 | import javax.persistence.EntityManagerFactory;
7 | import javax.persistence.Persistence;
8 | import javax.persistence.TypedQuery;
9 |
10 | import org.apache.shiro.SecurityUtils;
11 |
12 | public class ModelProvider {
13 |
14 | private EntityManager em;
15 |
16 | /**
17 | * Find data about logged user.
18 | *
19 | * @return current user data from database. If there is no such line, return null.
20 | */
21 | public UserPersonalData getCurrentUserData() {
22 | EntityManager em = getEntityManager();
23 | String loggedUser = (String)SecurityUtils.getSubject().getPrincipal();
24 | UserPersonalData loggedUserData = em.find(UserPersonalData.class, loggedUser);
25 | return loggedUserData;
26 | }
27 |
28 | /**
29 | * Find data about all users.
30 | *
31 | * @return users data from database.
32 | */ public List getAllUsersData() {
33 | EntityManager em = getEntityManager();
34 | TypedQuery query = em.createQuery("SELECT x FROM UserPersonalData x",UserPersonalData.class);
35 | List result = query.getResultList();
36 | return result;
37 | }
38 |
39 | private EntityManager getEntityManager() {
40 | if (!hasActiveEntityManager()) {
41 | EntityManagerFactory emf = Persistence.createEntityManagerFactory("SimpleShiroSecuredApplicationPU");
42 | em = emf.createEntityManager();
43 | }
44 | return em;
45 | }
46 |
47 | public void close() {
48 | if (hasActiveEntityManager()) {
49 | em.close();
50 | }
51 | }
52 |
53 | private boolean hasActiveEntityManager() {
54 | return em!=null && em.isOpen();
55 | }
56 |
57 | public void beginTransaction() {
58 | getEntityManager().getTransaction().begin();
59 | }
60 |
61 | public void persist(Object entity) {
62 | getEntityManager().persist(entity);
63 | }
64 |
65 | public void commit() {
66 | getEntityManager().getTransaction().commit();
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Licensed to the Apache Software Foundation (ASF) under one
3 | # or more contributor license agreements. See the NOTICE file
4 | # distributed with this work for additional information
5 | # regarding copyright ownership. The ASF licenses this file
6 | # to you under the Apache License, Version 2.0 (the
7 | # "License"); you may not use this file except in compliance
8 | # with the License. You may obtain a copy of the License at
9 | #
10 | # http://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing,
13 | # software distributed under the License is distributed on an
14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | # KIND, either express or implied. See the License for the
16 | # specific language governing permissions and limitations
17 | # under the License.
18 | #
19 | # This file is used to format all logging output
20 | log4j.rootLogger=TRACE, stdout
21 |
22 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender
23 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
24 | log4j.appender.stdout.layout.ConversionPattern=%d %-5p [%c]: %m%n
25 |
26 | # =============================================================================
27 | # 3rd Party Libraries
28 | # OFF, FATAL, ERROR, WARN, INFO, DEBUG, ALL
29 | # =============================================================================
30 | # ehcache caching manager:
31 | log4j.logger.net.sf.ehcache=WARN
32 |
33 | # Most all Apache libs:
34 | log4j.logger.org.apache=WARN
35 |
36 | # Quartz Enterprise Scheular (java 'cron' utility)
37 | log4j.logger.org.quartz=WARN
38 |
39 | # =============================================================================
40 | # Apache Shiro
41 | # =============================================================================
42 | # Shiro security framework
43 | log4j.logger.org.apache.shiro=TRACE
44 | #log4j.logger.org.apache.shiro.realm.text.PropertiesRealm=INFO
45 | #log4j.logger.org.apache.shiro.cache.ehcache.EhCache=INFO
46 | #log4j.logger.org.apache.shiro.io=INFO
47 | #log4j.logger.org.apache.shiro.web.servlet=INFO
48 | log4j.logger.org.apache.shiro.util.ThreadContext=INFO
49 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/authc/PrimaryPrincipalSameAuthenticationStrategy.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.authc;
2 |
3 | import org.apache.shiro.authc.AuthenticationException;
4 | import org.apache.shiro.authc.AuthenticationInfo;
5 | import org.apache.shiro.authc.AuthenticationToken;
6 | import org.apache.shiro.authc.pam.AllSuccessfulStrategy;
7 | import org.apache.shiro.realm.Realm;
8 | import org.apache.shiro.subject.PrincipalCollection;
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 |
12 | public class PrimaryPrincipalSameAuthenticationStrategy extends AllSuccessfulStrategy {
13 |
14 | private static final Logger log = LoggerFactory.getLogger(PrimaryPrincipalSameAuthenticationStrategy.class);
15 |
16 | @Override
17 | public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info, AuthenticationInfo aggregate, Throwable t) {
18 | validatePrimaryPrincipals(info, aggregate, realm);
19 | return super.afterAttempt(realm, token, info, aggregate, t);
20 | }
21 |
22 |
23 | private void validatePrimaryPrincipals(AuthenticationInfo info, AuthenticationInfo aggregate, Realm realm) {
24 | PrincipalCollection aggregPrincipals = aggregate.getPrincipals();
25 | if (aggregPrincipals==null || aggregPrincipals.isEmpty() || aggregPrincipals.getPrimaryPrincipal()==null)
26 | return ;
27 |
28 | if (info==null)
29 | return ;
30 |
31 | PrincipalCollection infoPrincipals = info.getPrincipals();
32 | if (infoPrincipals==null || infoPrincipals.isEmpty() || infoPrincipals.getPrimaryPrincipal()==null) {
33 | String message = "Primary principal is missing from " + realm.getName() + " result.";
34 | log.debug(message);
35 | throw new AuthenticationException(message);
36 | }
37 |
38 | Object aggregPrincipal = aggregPrincipals.getPrimaryPrincipal();
39 | Object infoPrincipal = infoPrincipals.getPrimaryPrincipal();
40 | if (!aggregPrincipal.equals(infoPrincipal)) {
41 | String message = "All realms are required to return the same primary principal. Offending realm: " + realm.getName();
42 | log.debug(message);
43 | throw new AuthenticationException(message);
44 | }
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/servlet/CertificateOrFormAuthenticationFilter.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.servlet;
2 |
3 | import java.security.cert.X509Certificate;
4 |
5 | import javax.servlet.ServletRequest;
6 | import javax.servlet.ServletResponse;
7 |
8 | import org.apache.shiro.authc.AuthenticationException;
9 | import org.apache.shiro.authc.AuthenticationToken;
10 | import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
11 | import org.meri.simpleshirosecuredapplication.authc.X509CertificateUsernamePasswordToken;
12 |
13 | public class CertificateOrFormAuthenticationFilter extends FormAuthenticationFilter {
14 |
15 | @Override
16 | protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
17 | return super.isLoginSubmission(request, response) || isCertificateLogInAttempt(request, response);
18 | }
19 |
20 | private boolean isCertificateLogInAttempt(ServletRequest request, ServletResponse response) {
21 | return hasCertificate(request) && !getSubject(request, response).isAuthenticated();
22 | }
23 |
24 | private boolean hasCertificate(ServletRequest request) {
25 | return null != getCertificate(request);
26 | }
27 |
28 | private X509Certificate getCertificate(ServletRequest request) {
29 | X509Certificate[] attribute = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
30 | return attribute == null ? null : attribute[0];
31 | }
32 |
33 | @Override
34 | protected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) {
35 | boolean rememberMe = isRememberMe(request);
36 | String host = getHost(request);
37 | X509Certificate certificate = getCertificate(request);
38 | return createToken(username, password, rememberMe, host, certificate);
39 | }
40 |
41 | protected AuthenticationToken createToken(String username, String password, boolean rememberMe, String host, X509Certificate certificate) {
42 | return new X509CertificateUsernamePasswordToken(username, password, rememberMe, host, certificate);
43 | }
44 |
45 | @Override
46 | protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
47 | String message = ae.getMessage();
48 | request.setAttribute(getFailureKeyAttribute(), message);
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/servlet/PerformFunctionAndGoBackServlet.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.servlet;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.RequestDispatcher;
6 | import javax.servlet.Servlet;
7 | import javax.servlet.ServletException;
8 | import javax.servlet.http.HttpServlet;
9 | import javax.servlet.http.HttpServletRequest;
10 | import javax.servlet.http.HttpServletResponse;
11 |
12 | import org.apache.log4j.Logger;
13 | import org.apache.shiro.ShiroException;
14 | import org.meri.simpleshirosecuredapplication.actions.Actions;
15 |
16 | public class PerformFunctionAndGoBackServlet extends HttpServlet implements Servlet {
17 |
18 | private static transient final Logger log = Logger.getLogger(PerformFunctionAndGoBackServlet.class);
19 |
20 | private static final long serialVersionUID = -7896114563632467947L;
21 |
22 | @Override
23 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
24 | doPost(req, resp);
25 | }
26 |
27 | @Override
28 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
29 | String action = request.getParameter("action");
30 | String actionResult = performAction(action);
31 | request.setAttribute(ServletConstants.actionResultMessage, actionResult);
32 |
33 | // forward the request and response back to original page
34 | String originalPage = request.getParameter(ServletConstants.originalPage);
35 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(originalPage);
36 | dispatcher.forward(request, response);
37 | }
38 |
39 | private String performAction(String actionName) {
40 | try {
41 | Actions action = findAction(actionName);
42 | String result = action == null ? null : action.doIt();
43 | log.debug("Performed function with result: " + result);
44 | return result;
45 | } catch (ShiroException ex) {
46 | log.debug("Function failed with " + ex.getMessage() + " message.");
47 | return "Error: " + ex.getMessage();
48 | }
49 | }
50 |
51 | private Actions findAction(String actionName) {
52 | if (actionName == null)
53 | return null;
54 |
55 | Actions[] values = Actions.values();
56 |
57 | for (Actions action : values) {
58 | if (actionName.equals(action.getName()))
59 | return action;
60 | }
61 | return null;
62 | }
63 |
64 | public String getUrl(HttpServletRequest request) {
65 | String reqUrl = request.getRequestURL().toString();
66 | String queryString = request.getQueryString(); // d=789
67 | if (queryString != null) {
68 | reqUrl += "?" + queryString;
69 | }
70 | return reqUrl;
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/servlet/AccountPageServlet.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.servlet;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.RequestDispatcher;
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 |
10 | import org.apache.shiro.SecurityUtils;
11 | import org.meri.simpleshirosecuredapplication.model.ModelProvider;
12 | import org.meri.simpleshirosecuredapplication.model.UserPersonalData;
13 | import org.slf4j.Logger;
14 | import org.slf4j.LoggerFactory;
15 |
16 | @SuppressWarnings("serial")
17 | public class AccountPageServlet extends AbstractFieldsHandlingServlet {
18 |
19 | private static final Logger log = LoggerFactory.getLogger(AccountPageServlet.class);
20 |
21 | @Override
22 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
23 | doPost(req, resp);
24 | }
25 |
26 | @Override
27 | protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
28 | String loggedPrincipal = (String) SecurityUtils.getSubject().getPrincipal();
29 | String firstname = getField(request, "firstname");
30 | String lastname = getField(request, "lastname");
31 | String about = getField(request, "about");
32 |
33 | try {
34 | saveData(loggedPrincipal, firstname, lastname, about);
35 | request.setAttribute(ServletConstants.actionResultMessage, "Saved was successful.");
36 | } catch (Exception ex) {
37 | log.error("Could not save data.", ex);
38 | request.setAttribute(ServletConstants.actionResultMessage, "Saved unsuccessful :(.");
39 | }
40 |
41 | // forward the request and response back to original page
42 | String originalPage = request.getParameter(ServletConstants.originalPage);
43 | RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(originalPage);
44 | dispatcher.forward(request, response);
45 | }
46 |
47 | private void saveData(String loggedPrincipal, String firstname, String lastname, String about) {
48 | ModelProvider mp = new ModelProvider();
49 | try {
50 | UserPersonalData data = mp.getCurrentUserData();
51 | mp.beginTransaction();
52 | if (data == null) {
53 | data = new UserPersonalData();
54 | updateUser(data, loggedPrincipal, firstname, lastname, about);
55 | mp.persist(data);
56 | } else {
57 | updateUser(data, loggedPrincipal, firstname, lastname, about);
58 | }
59 | mp.commit();
60 | } finally {
61 | mp.close();
62 | }
63 | }
64 |
65 | private void updateUser(UserPersonalData data, String loggedPrincipal, String firstname, String lastname, String about) {
66 | data.setUserName(loggedPrincipal);
67 | data.setFirstname(firstname);
68 | data.setLastname(lastname);
69 | data.setAbout(about);
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 | Simple Secure Application
7 |
8 |
9 |
10 | PerformFunctionAndGoBackServlet
11 | org.meri.simpleshirosecuredapplication.servlet.PerformFunctionAndGoBackServlet
12 | 1
13 |
14 |
15 |
16 | AccountPageServlet
17 | org.meri.simpleshirosecuredapplication.servlet.AccountPageServlet
18 | 1
19 |
20 |
21 |
22 | PerformFunctionAndGoBackServlet
23 | /simpleshirosecuredapplication/masterservlet
24 |
25 |
26 |
27 | AccountPageServlet
28 | /simpleshirosecuredapplication/accountpageservlet
29 |
30 |
31 |
32 | index.jsp
33 |
34 |
35 |
36 |
37 |
38 | ShiroFilter
39 | org.apache.shiro.web.servlet.IniShiroFilter
40 |
41 | configPath
42 | classpath:Shiro.ini
43 |
44 |
45 |
46 |
47 | ShiroFilter
48 | /*
49 |
50 |
51 |
52 |
53 |
54 |
55 | Derby Connection
56 | jdbc/SimpleShiroSecuredApplicationDB
57 | javax.sql.DataSource
58 | Container
59 |
60 |
61 |
62 |
63 | liquibase.changelog
64 | src/main/resources/db.changelog.xml
65 |
66 |
67 |
68 | liquibase.datasource
69 | jdbc/SimpleShiroSecuredApplicationDB
70 |
71 |
72 |
74 |
75 | liquibase.integration.servlet.LiquibaseServletListener
76 |
77 |
78 |
79 | persistence/SimpleShiroSecuredApplicationPU
80 | SimpleShiroSecuredApplicationPU
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/main/resources/Shiro.ini:
--------------------------------------------------------------------------------
1 | [main]
2 | # realms to be used
3 | certificateRealm=org.meri.simpleshirosecuredapplication.realm.X509CertificateRealm
4 | certificateRealm.trustStore=src/main/resources/truststore
5 | certificateRealm.trustStorePassword=secret
6 | certificateRealm.jndiDataSourceName=jdbc/SimpleShiroSecuredApplicationDB
7 |
8 | saltedJdbcRealm=org.meri.simpleshirosecuredapplication.realm.JNDIAndSaltAwareJdbcRealm
9 | # any object property is automatically configurable in Shiro.ini file
10 | saltedJdbcRealm.jndiDataSourceName=jdbc/SimpleShiroSecuredApplicationDB
11 | # the realm should handle also authorization
12 | saltedJdbcRealm.permissionsLookupEnabled=true
13 | # If not filled, subclasses of JdbcRealm assume "select password from users where username = ?"
14 | # first result column is password, second result column is salt
15 | saltedJdbcRealm.authenticationQuery = select password, salt from sec_users where name = ?
16 | # If not filled, subclasses of JdbcRealm assume "select role_name from user_roles where username = ?"
17 | saltedJdbcRealm.userRolesQuery = select role_name from sec_users_roles where user_name = ?
18 | # If not filled, subclasses of JdbcRealm assume "select permission from roles_permissions where role_name = ?"
19 | saltedJdbcRealm.permissionsQuery = select permissions from sec_roles_permissions where role_name = ?
20 |
21 | # password hashing specification, put something big for hasIterations
22 | sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
23 | sha256Matcher.hashAlgorithmName=SHA-256
24 | sha256Matcher.hashIterations=1
25 | saltedJdbcRealm.credentialsMatcher = $sha256Matcher
26 |
27 | # filter configuration
28 | certificateFilter = org.meri.simpleshirosecuredapplication.servlet.CertificateOrFormAuthenticationFilter
29 | # request parameter with login error information; if not present filter assumes 'shiroLoginFailure'
30 | certificateFilter.failureKeyAttribute=simpleShiroApplicationLoginFailure
31 | # specify login page
32 | certificateFilter.loginUrl = /simpleshirosecuredapplication/account/login.jsp
33 | # name of request parameter with username; if not present filter assumes 'username'
34 | certificateFilter.usernameParam = user
35 | # name of request parameter with password; if not present filter assumes 'password'
36 | certificateFilter.passwordParam = pass
37 | # does the user wish to be remembered?; if not present filter assumes 'rememberMe'
38 | certificateFilter.rememberMeParam = remember
39 | # redirect after successful login
40 | certificateFilter.successUrl = /simpleshirosecuredapplication/account/personalaccountpage.jsp
41 |
42 | # roles filter: redirect to error page if user does not have access rights
43 | roles.unauthorizedUrl = /simpleshirosecuredapplication/account/accessdenied.jsp
44 |
45 | # multi-realms strategy
46 | # authenticationStrategy=org.meri.simpleshirosecuredapplication.authc.PrimaryPrincipalSameAuthenticationStrategy
47 | # securityManager.authenticator.authenticationStrategy=$authenticationStrategy
48 |
49 | [urls]
50 | # force ssl for login page
51 | /simpleshirosecuredapplication/account/login.jsp=ssl[8443], certificateFilter
52 |
53 | # only users with some roles are allowed to use role-specific pages
54 | /simpleshirosecuredapplication/repairmen/**=certificateFilter, roles[repairman]
55 | /simpleshirosecuredapplication/sales/**=certificateFilter, roles[sales]
56 | /simpleshirosecuredapplication/scientists/**=certificateFilter, roles[scientist]
57 | /simpleshirosecuredapplication/adminarea/**=certificateFilter, roles[Administrator]
58 |
59 | # enable certificateFilter filter for all application pages
60 | /simpleshirosecuredapplication/**=certificateFilter
61 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/realm/JNDIAndSaltAwareJdbcRealm.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.realm;
2 |
3 | import java.sql.Connection;
4 | import java.sql.PreparedStatement;
5 | import java.sql.ResultSet;
6 | import java.sql.SQLException;
7 |
8 | import javax.naming.InitialContext;
9 | import javax.naming.NamingException;
10 | import javax.sql.DataSource;
11 |
12 | import org.apache.shiro.authc.AuthenticationException;
13 | import org.apache.shiro.authc.AuthenticationInfo;
14 | import org.apache.shiro.authc.AuthenticationToken;
15 | import org.apache.shiro.authc.SimpleAuthenticationInfo;
16 | import org.apache.shiro.authc.UsernamePasswordToken;
17 | import org.apache.shiro.authz.AuthorizationException;
18 | import org.apache.shiro.realm.jdbc.JdbcRealm;
19 | import org.apache.shiro.util.JdbcUtils;
20 | import org.apache.shiro.util.SimpleByteSource;
21 | import org.slf4j.Logger;
22 | import org.slf4j.LoggerFactory;
23 |
24 | /**
25 | * This realm has all {@link JdbcRealm} capabilities. It also supports JNDI as datasource source and
26 | * can add salt to passwords.
27 | */
28 | public class JNDIAndSaltAwareJdbcRealm extends JdbcRealm {
29 |
30 | private static final Logger log = LoggerFactory.getLogger(JNDIAndSaltAwareJdbcRealm.class);
31 |
32 | protected String jndiDataSourceName;
33 |
34 | public JNDIAndSaltAwareJdbcRealm() {
35 | }
36 |
37 | public String getJndiDataSourceName() {
38 | return jndiDataSourceName;
39 | }
40 |
41 | public void setJndiDataSourceName(String jndiDataSourceName) {
42 | this.jndiDataSourceName = jndiDataSourceName;
43 | this.dataSource = getDataSourceFromJNDI(jndiDataSourceName);
44 | }
45 |
46 | private DataSource getDataSourceFromJNDI(String jndiDataSourceName) {
47 | try {
48 | InitialContext ic = new InitialContext();
49 | return (DataSource) ic.lookup(jndiDataSourceName);
50 | } catch (NamingException e) {
51 | log.error("JNDI error while retrieving " + jndiDataSourceName, e);
52 | throw new AuthorizationException(e);
53 | }
54 | }
55 |
56 | @Override
57 | protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
58 | //identify account to log to
59 | UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
60 | String username = userPassToken.getUsername();
61 |
62 | if (username == null) {
63 | log.debug("Username is null.");
64 | return null;
65 | }
66 |
67 | // read password hash and salt from db
68 | PasswdSalt passwdSalt = getPasswordForUser(username);
69 |
70 | if (passwdSalt == null) {
71 | log.debug("No account found for user [" + username + "]");
72 | return null;
73 | }
74 |
75 | // return salted credentials
76 | SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, passwdSalt.password, getName());
77 | info.setCredentialsSalt(new SimpleByteSource(passwdSalt.salt));
78 |
79 | return info;
80 | }
81 |
82 | private PasswdSalt getPasswordForUser(String username) {
83 | PreparedStatement statement = null;
84 | ResultSet resultSet = null;
85 | Connection conn = null;
86 | try {
87 | conn = dataSource.getConnection();
88 | statement = conn.prepareStatement(authenticationQuery);
89 | statement.setString(1, username);
90 |
91 | resultSet = statement.executeQuery();
92 |
93 | boolean hasAccount = resultSet.next();
94 | if (!hasAccount)
95 | return null;
96 |
97 | String salt = null;
98 | String password = resultSet.getString(1);
99 | if (resultSet.getMetaData().getColumnCount() > 1)
100 | salt = resultSet.getString(2);
101 |
102 | if (resultSet.next()) {
103 | throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
104 | }
105 |
106 | return new PasswdSalt(password, salt);
107 | } catch (SQLException e) {
108 | final String message = "There was a SQL error while authenticating user [" + username + "]";
109 | if (log.isErrorEnabled()) {
110 | log.error(message, e);
111 | }
112 | throw new AuthenticationException(message, e);
113 |
114 | } finally {
115 | JdbcUtils.closeResultSet(resultSet);
116 | JdbcUtils.closeStatement(statement);
117 | JdbcUtils.closeConnection(conn);
118 | }
119 | }
120 |
121 | }
122 |
123 | class PasswdSalt {
124 |
125 | public String password;
126 | public String salt;
127 |
128 | public PasswdSalt(String password, String salt) {
129 | super();
130 | this.password = password;
131 | this.salt = salt;
132 | }
133 |
134 | }
--------------------------------------------------------------------------------
/src/test/java/org/meri/simpleshirosecuredapplication/test/AbstractContainerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | package org.meri.simpleshirosecuredapplication.test;
20 |
21 | import static org.junit.Assert.assertTrue;
22 |
23 | import org.junit.Before;
24 | import org.junit.BeforeClass;
25 | import org.mortbay.jetty.Connector;
26 | import org.mortbay.jetty.Server;
27 | import org.mortbay.jetty.nio.SelectChannelConnector;
28 | import org.mortbay.jetty.security.SslSocketConnector;
29 | import org.mortbay.jetty.webapp.WebAppContext;
30 |
31 | import com.gargoylesoftware.htmlunit.WebClient;
32 |
33 | public abstract class AbstractContainerTest {
34 | protected static PauseableServer server;
35 |
36 | protected static final int port = 9180;
37 | protected static final int sslPort = 8443;
38 |
39 | protected static final String BASEURI = "http://localhost:" + port + "/";
40 |
41 | protected final WebClient webClient = new WebClient();
42 |
43 | private static String[] configurationClasses =
44 | {
45 | "org.mortbay.jetty.webapp.JettyWebXmlConfiguration",
46 | "org.mortbay.jetty.webapp.WebInfConfiguration",
47 | "org.mortbay.jetty.plus.webapp.EnvConfiguration",
48 | "org.mortbay.jetty.plus.webapp.Configuration",
49 | "org.mortbay.jetty.webapp.TagLibConfiguration"
50 | } ;
51 |
52 |
53 | @BeforeClass
54 | public static void startContainer() throws Exception {
55 | if (server == null) {
56 | server = new PauseableServer();
57 | Connector connector = createHttpConnector();
58 | SslSocketConnector sslConnector = createSslConnector();
59 | server.setConnectors(new Connector[]{connector, sslConnector});
60 |
61 | WebAppContext context = createWebAppContext();
62 | server.setHandler(context);
63 |
64 | server.start();
65 | assertTrue(server.isStarted());
66 | }
67 | }
68 |
69 | private static WebAppContext createWebAppContext() {
70 | WebAppContext context = new WebAppContext("src/main/webapp", "/");
71 | context.setConfigurationClasses(configurationClasses);
72 |
73 | return context;
74 | }
75 |
76 | private static SslSocketConnector createSslConnector() {
77 | SslSocketConnector sslConnector = new SslSocketConnector();
78 | sslConnector.setPort(sslPort);
79 | sslConnector.setKeyPassword("secret");
80 | sslConnector.setKeystore("src/test/resources/keystore");
81 | sslConnector.setTrustPassword("secret");
82 | sslConnector.setTruststore("src/main/resources/truststore");
83 | sslConnector.setPassword("secret");
84 | sslConnector.setWantClientAuth(true);
85 | //sslConnector.setNeedClientAuth(true);
86 |
87 | return sslConnector;
88 | }
89 |
90 | private static Connector createHttpConnector() {
91 | Connector connector = new SelectChannelConnector();
92 | connector.setPort(port);
93 | return connector;
94 | }
95 |
96 | @Before
97 | public void beforeTest() {
98 | webClient.setThrowExceptionOnFailingStatusCode(true);
99 | }
100 |
101 | public void pauseServer(boolean paused) {
102 | if (server != null) server.pause(paused);
103 | }
104 |
105 | public static class PauseableServer extends Server {
106 | public synchronized void pause(boolean paused) {
107 | try {
108 | if (paused) for (Connector connector : getConnectors())
109 | connector.stop();
110 | else for (Connector connector : getConnectors())
111 | connector.start();
112 | } catch (Exception e) {
113 | }
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 | SimpleSecuredApplication
5 | SimpleSecuredApplication
6 | war
7 | 0.0.1-SNAPSHOT
8 | SimpleSecuredApplication Maven Webapp
9 | http://maven.apache.org
10 |
11 |
12 |
13 | 1.6
14 | 1.6.1
15 | 1.2.0-SNAPSHOT
16 |
17 |
18 | 4.8.2
19 | 6.1.26
20 |
21 |
22 |
23 |
24 | org.liquibase
25 | liquibase-core
26 | 2.0.1
27 |
28 |
29 | org.apache.derby
30 | derby
31 | 10.7.1.1
32 |
33 |
34 | org.apache.shiro
35 | shiro-core
36 | ${shiro.version}
37 |
38 |
39 | org.apache.shiro
40 | shiro-web
41 | ${shiro.version}
42 |
43 |
44 | junit
45 | junit
46 | ${junit.version}
47 | test
48 |
49 |
50 | javax.servlet
51 | jstl
52 | 1.1.2
53 |
54 |
55 | javax.servlet
56 | servlet-api
57 | 2.5
58 | provided
59 |
60 |
61 | org.slf4j
62 | slf4j-log4j12
63 | ${slf4j.version}
64 | runtime
65 |
66 |
67 | log4j
68 | log4j
69 | 1.2.16
70 | runtime
71 |
72 |
73 | net.sourceforge.htmlunit
74 | htmlunit
75 | 2.6
76 | test
77 |
78 |
79 | org.mortbay.jetty
80 | jetty
81 | ${jetty.version}
82 | test
83 |
84 |
85 | org.mortbay.jetty
86 | jsp-2.1-jetty
87 | ${jetty.version}
88 | test
89 |
90 |
91 | org.mortbay.jetty
92 | jetty-naming
93 | ${jetty.version}
94 | test
95 |
96 |
97 | org.mortbay.jetty
98 | jetty-plus
99 | ${jetty.version}
100 | test
101 |
102 |
103 | taglibs
104 | standard
105 | 1.1.2
106 |
107 |
108 | org.apache.openjpa
109 | apache-openjpa
110 | 2.1.0
111 | pom
112 | compile
113 |
114 |
115 | org.owasp
116 | antisamy
117 | 1.4
118 | jar
119 | compile
120 |
121 |
122 |
123 |
124 | SimpleSecuredApplication
125 |
126 |
127 |
128 | maven-surefire-plugin
129 |
130 | never
131 |
132 |
133 |
134 | org.mortbay.jetty
135 | maven-jetty-plugin
136 | ${jetty.version}
137 |
138 | /
139 |
140 |
141 | 9080
142 | 60000
143 |
144 |
145 |
146 | ./target/yyyy_mm_dd.request.log
147 | 90
148 | true
149 | false
150 | GMT
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | maven2-repository.dev.java.net
161 | Java.net Repository for Maven
162 | http://download.java.net/maven/2/
163 | default
164 |
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/src/main/resources/antisamy-tinymce-1.4.4.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
32 |
33 |
34 |
36 |
37 |
39 |
40 |
41 |
42 |
49 |
50 |
54 |
55 |
56 |
57 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
81 |
82 |
83 |
84 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
97 |
98 |
99 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | g
115 | grin
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
--------------------------------------------------------------------------------
/src/main/java/org/meri/simpleshirosecuredapplication/realm/X509CertificateRealm.java:
--------------------------------------------------------------------------------
1 | package org.meri.simpleshirosecuredapplication.realm;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.IOException;
5 | import java.math.BigInteger;
6 | import java.security.KeyStore;
7 | import java.security.cert.CertPathBuilder;
8 | import java.security.cert.CertPathBuilderResult;
9 | import java.security.cert.CertStore;
10 | import java.security.cert.CertStoreParameters;
11 | import java.security.cert.CollectionCertStoreParameters;
12 | import java.security.cert.PKIXBuilderParameters;
13 | import java.security.cert.X509CRL;
14 | import java.security.cert.X509CertSelector;
15 | import java.security.cert.X509Certificate;
16 | import java.sql.Connection;
17 | import java.sql.PreparedStatement;
18 | import java.sql.ResultSet;
19 | import java.sql.SQLException;
20 | import java.util.Arrays;
21 | import java.util.Collection;
22 | import java.util.Collections;
23 | import java.util.List;
24 |
25 | import javax.naming.InitialContext;
26 | import javax.naming.NamingException;
27 | import javax.sql.DataSource;
28 |
29 | import org.apache.shiro.authc.AuthenticationException;
30 | import org.apache.shiro.authc.AuthenticationInfo;
31 | import org.apache.shiro.authc.AuthenticationToken;
32 | import org.apache.shiro.authc.SimpleAuthenticationInfo;
33 | import org.apache.shiro.authz.AuthorizationException;
34 | import org.apache.shiro.realm.Realm;
35 | import org.apache.shiro.util.JdbcUtils;
36 | import org.apache.shiro.util.Nameable;
37 | import org.apache.xerces.impl.dv.util.Base64;
38 | import org.meri.simpleshirosecuredapplication.authc.X509CertificateAuthenticationToken;
39 | import org.slf4j.Logger;
40 | import org.slf4j.LoggerFactory;
41 |
42 | public class X509CertificateRealm implements Realm, Nameable {
43 |
44 | protected static final String DEFAULT_ACCOUNT_TO_CERTIFICATE_QUERY = "select name from sec_users where serialnumber = ? and issuername = ?";
45 |
46 | private String name;
47 | private String trustStorePassword;
48 | private String trustStore;
49 |
50 | protected String jndiDataSourceName;
51 | protected String accountToCertificateQuery = DEFAULT_ACCOUNT_TO_CERTIFICATE_QUERY;
52 | protected DataSource dataSource;
53 |
54 | private static final Logger log = LoggerFactory.getLogger(X509CertificateRealm.class);
55 |
56 | @Override
57 | public boolean supports(AuthenticationToken token) {
58 | if (token!=null)
59 | return token instanceof X509CertificateAuthenticationToken;
60 |
61 | return false;
62 | }
63 |
64 | @Override
65 | public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
66 | // the cast is legal, since Shiro will let in only X509CertificateAuthenticationToken tokens
67 | X509CertificateAuthenticationToken certificateToken = (X509CertificateAuthenticationToken) token;
68 | X509Certificate certificate = certificateToken.getCertificate();
69 |
70 | // verify certificate
71 | if (!certificateOK(certificate)) {
72 | return null;
73 | }
74 |
75 | // the issuer name and serial number uniquely identifies certificate
76 | BigInteger serialNumber = certificate.getSerialNumber();
77 | String issuerName = certificate.getIssuerDN().getName();
78 |
79 | // find account associated with certificate
80 | String username = findUsernameToCertificate(issuerName, serialNumber);
81 | if (username == null) {
82 | // return null as no account was found
83 | return null;
84 | }
85 |
86 | // sucesfull verification, return authentication info
87 | return new SimpleAuthenticationInfo(username, certificate, getName());
88 | }
89 |
90 | /**
91 | * The most simple certificate validation. If we are in web application context, the certificate is already checked
92 | * by web server and valid, so this method is useless there.
93 | *
94 | * @param certificate to be validated
95 | *
96 | * @return true if the certificate is valid. false otherwise.
97 | */
98 | private boolean certificateOK(X509Certificate certificate) {
99 | if (certificate == null)
100 | return false;
101 |
102 | List chain = Arrays.asList(certificate);
103 | FileInputStream stream = null;
104 |
105 | /* Construct a valid path. */
106 | try {
107 | stream = new FileInputStream(getTrustStore());
108 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
109 | keyStore.load(stream, getTrustStorePassword().toCharArray());
110 |
111 | X509CertSelector target = new X509CertSelector();
112 | target.setCertificate(certificate);
113 | PKIXBuilderParameters params = new PKIXBuilderParameters(keyStore, target);
114 | CertStoreParameters intermediates = new CollectionCertStoreParameters(chain);
115 | params.addCertStore(CertStore.getInstance("Collection", intermediates));
116 |
117 | // Lets ignore certificate revocation list for example purposes
118 | Collection crls = Collections.emptyList();
119 | CertStoreParameters revoked = new CollectionCertStoreParameters(crls);
120 | params.addCertStore(CertStore.getInstance("Collection", revoked));
121 |
122 | // If build() returns successfully, the certificate is valid.
123 | @SuppressWarnings("unused")
124 | CertPathBuilderResult r = CertPathBuilder.getInstance("PKIX").build(params);
125 | } catch (Exception ex) {
126 | log.debug("Certificate validation failed. ", ex);
127 | return false;
128 | } finally {
129 | try {
130 | stream.close();
131 | } catch (IOException e) {
132 | log.error("Could not close truststore stream.", e);
133 | }
134 | }
135 |
136 | return true;
137 | }
138 |
139 | public String getTrustStorePassword() {
140 | return trustStorePassword;
141 | }
142 |
143 | public void setTrustStorePassword(String trustStorePassword) {
144 | this.trustStorePassword = trustStorePassword;
145 | }
146 |
147 | public String getTrustStore() {
148 | return trustStore;
149 | }
150 |
151 | public void setTrustStore(String trustStore) {
152 | this.trustStore = trustStore;
153 | }
154 |
155 | public String getAccountToCertificateQuery() {
156 | return accountToCertificateQuery;
157 | }
158 |
159 | public void setAccountToCertificateQuery(String accountToCertificateQuery) {
160 | this.accountToCertificateQuery = accountToCertificateQuery;
161 | }
162 |
163 | public String getJndiDataSourceName() {
164 | return jndiDataSourceName;
165 | }
166 |
167 | public void setJndiDataSourceName(String jndiDataSourceName) {
168 | this.jndiDataSourceName = jndiDataSourceName;
169 | this.dataSource = getDataSourceFromJNDI(jndiDataSourceName);
170 | }
171 |
172 | private DataSource getDataSourceFromJNDI(String jndiDataSourceName) {
173 | try {
174 | InitialContext ic = new InitialContext();
175 | return (DataSource) ic.lookup(jndiDataSourceName);
176 | } catch (NamingException e) {
177 | log.error("JNDI error while retrieving " + jndiDataSourceName, e);
178 | throw new AuthorizationException(e);
179 | }
180 | }
181 |
182 | private String findUsernameToCertificate(String issuerName, BigInteger serialNumber) {
183 | String base64Serial = Base64.encode(serialNumber.toByteArray());
184 | return getAccountName(issuerName, base64Serial);
185 | }
186 |
187 | private String getAccountName(String issuerName, String base64Serial) {
188 | PreparedStatement statement = null;
189 | ResultSet resultSet = null;
190 | Connection conn = null;
191 | try {
192 | conn = dataSource.getConnection();
193 | statement = conn.prepareStatement(accountToCertificateQuery);
194 | statement.setString(1, base64Serial);
195 | statement.setString(2, issuerName);
196 |
197 | resultSet = statement.executeQuery();
198 |
199 | boolean hasAccount = resultSet.next();
200 | if (!hasAccount)
201 | return null;
202 |
203 | String username = resultSet.getString(1);
204 |
205 | if (resultSet.next()) {
206 | throw new AuthenticationException("More than one account for thre certificate.");
207 | }
208 |
209 | return username;
210 | } catch (SQLException e) {
211 | final String message = "There was a SQL error while authenticating user.";
212 | if (log.isErrorEnabled()) {
213 | log.error(message, e);
214 | }
215 | throw new AuthenticationException(message, e);
216 |
217 | } finally {
218 | JdbcUtils.closeResultSet(resultSet);
219 | JdbcUtils.closeStatement(statement);
220 | JdbcUtils.closeConnection(conn);
221 | }
222 | }
223 |
224 | public String getName() {
225 | return name;
226 | }
227 |
228 | public void setName(String name) {
229 | this.name = name;
230 | }
231 |
232 | }
233 |
--------------------------------------------------------------------------------
/src/main/resources/db.changelog.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 | Create table structure for users, passwords, roles and permissions.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
44 |
45 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
61 |
62 |
63 |
64 |
65 |
66 | Create initial users, roles and permissions.
67 |
68 |
69 |
70 |
72 |
73 |
74 |
75 |
76 |
78 |
79 |
80 |
81 |
82 |
84 |
85 |
86 |
87 |
88 |
90 |
91 |
92 |
93 |
94 |
96 |
97 |
98 |
99 |
100 |
102 |
103 |
104 |
105 |
106 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | Add certificate unique id to sec_users table.
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | Associate certificates with initial user accounts.
185 |
186 |
187 |
188 |
189 | name='administrator'
190 |
191 |
192 |
193 |
194 | name='friendlyrepairman'
195 |
196 |
197 |
198 |
199 | name='unfriendlyrepairman'
200 |
201 |
202 |
203 |
204 | name='mathematician'
205 |
206 |
207 |
208 |
209 | name='servicessales'
210 |
211 |
212 |
213 |
214 | name='productsales'
215 |
216 |
217 |
218 |
219 | name='physicien'
220 |
221 |
222 |
223 |
224 |
225 | Add user personal data such as first name, last name, hobby etc to the application.
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
245 |
246 |
247 |
--------------------------------------------------------------------------------