@RequestScoped
@RequiresUser
public class MyBean { }
@RequestScoped
@RolesAllowed("role")
public class MyRoleBean { }
|
Handy Hint
|
Shiro v1 version notice
As of February 28, 2024, Shiro v1 was superseded by v2.
As of June 29, 2026, Shiro v2 was superseded by v3.
|
Apache Shiro Jakarta EE module makes it transparent to use Shiro features in Jakarta EE applications
with minimal configuration. It makes annotations such as @RequiresRoles available in Jakarta EE (CDI, EJB, etc.) code.
|
Jakarta EE integration is available in Shiro 2.0 or later. The module is compatible with Jakarta EE 10 through Jakarta EE 11 or later. It may work with earlier versions of Jakarta EE but was not tested with those. |
Include the shiro-jakarta-ee dependency in you application classpath (we recommend using a tool such as Apache Maven or Gradle to manage this).
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-jakarta-ee</artifactId>
<version>3.0.0</version>
</dependency>
compile 'org.apache.shiro:shiro-jakarta-ee:3.0.0'
libraryDependencies += "org.apache.shiro" % "shiro-jakarta-ee" % "3.0.0"
<dependency org="org.apache.shiro" name="shiro-jakarta-ee" rev="3.0.0"/>
[org.apache.shiro/shiro-jakarta-ee "3.0.0"]
'org.apache.shiro:shiro-jakarta-ee:jar:3.0.0'
Jakarta EE module depends on CDI and Jax-RS submodules to fully integrate with the complete Jakarta EE API (Web Profile). If that is not desired, CDI and Jax-RS submodules can be used separately.
Configure Shiro automatically with sensible defaults for Jakarta EE, with minimal, or no configuration aside from shiro.ini.
Use shiro.ini as usual to secure web applications, Jax-RS paths and endpoints.
Forms are automatically saved if sessions expire and seamlessly submitted upon subsequent login.
Use Shiro-secured application behind a load balancer or an SSL-terminating proxy (haproxy, nginx, etc.) easily.
Use @Named CDI beans in shiro.ini.
Inject Shiro Subject, Principal, Session and SecurityManager into CDI, EJB beans and Jax-RS endpoints.
Use Shiro and Jakarta EE Security annotations (i.e. @RequiresRole) to protect CDI, EJB (local and remote) beans (part of CDI module) and Jax-RS endpoints (part of Jax-RS module)
Use Jakarta Faces (JSF) tags.
Make Shiro’s login flows Jakarta Faces (JSF) Ajax-aware.
Smart redirect flow based on custom code and fallback pages.
In addition to all Shiro annotations, Jakarta EE module allows to specify Jakarta EE security annotations such as @RolesAllowed, @DenyAll and @PermitAll on your beans
No additional configuration is required to use Shiro Jakarta EE module. The module is bootstrapped automatically.
Only shiro.ini is needed and can be configured as described in Web Configuration
See configuration options below if any customization is needed.
Use Shiro and Jakarta Security annotations on any CDI bean, with no additional annotations required:
@RequestScoped
@RequiresUser
public class MyBean { }
@RequestScoped
@RolesAllowed("role")
public class MyRoleBean { }
Shiro APIs can be @Inject into CDI and EJB beans:
@ApplicationScoped
public class MyBean {
@Inject
SecurityManager manager;
@Inject
Subject subject;
@Inject
@Principal
Supplier<MyUserAccount> userAccount;
@Inject
Session session;
@Inject
@NoSessionCreation
Session optionalSession;
}
Subject, Session and @Principal are always treated as Request-Scoped beans. They are injectable into any Jakarta EE bean including Jax-RS, Servlet and other CDI beans.
If Session is annotated with @NoSessionCreation and there is no existing session, InvalidSessionException is thrown when accessing the Injected session.
Any Shiro principal object can be injected if annotated by @Principal. It must be injected as Supplier<MyPrincipalClass>, and Supplier.get() may return null if there are no principals available of the injected type.
Jakarta EE integration module was inspired by this OmniFaces article and brings everything together to seamlessly create secure Jakarta EE applications easily and with minimal configuration. The module works "the Shiro way" and uses shiro.ini in a straight-forward and intuitive way.
RememberMe functionality is disabled by default. You can enable it easily by adding the below to shiro.ini:
authc.useRemembered = true
RememberMe uses secure cookies by default. If you are running in non-HTTPS environment, you can disable secure cookies by adding the following to shiro.ini (this is not a recommended configuration, since it can lead to security vulnerabilities, but it is available for testing and development purposes):
securityManager.rememberMeManager.cookie.secure = false
When user fails to log in, Shiro will automatically delay the failure response for a number of seconds. This can be one of the strategies to prevent brute force attacks.
| Be careful utilizing this technique, as it could be a vector for a denial-of-service attack. Servers with virtual thread support (Project Loom) will not be affected by the DDOS vector. |
Add the below to shiro.ini:
authc.loginFailedWaitTime = 5
web.xmlNo configuration is required. The module is bootstrapped automatically.
To disable automatic bootstrapping, add the following to web.xml:
<context-param>
<param-name>org.apache.shiro.ee.disabled</param-name>
<param-value>true</param-value>
</context-param>
The module adds ShiroFilter to the Servlet configuration. For most cases, the filter ordering works correctly out of the box. However, some cases require to reorder filters. Filter ordering follows the order of <filter-mapping> elements in web.xml:
<!-- Enforce Filter Ordering (Optional) -->
... other filters ...
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern/>
</filter-mapping>
... other filters ...
Ordinarily, Shiro sets session cookies to be secure, unless you are in Jakarta Faces' Development mode.
Make sure to run in other than Development mode in production, so that secure cookies are used.
This only applies to Servlet Container-managed sessions, and not Shiro native sessions.
If secure session cookies are not desired, you can disable them by adding the following to web.xml:
<context-param>
<param-name>org.apache.shiro.ee.secure-session-cookie.disabled</param-name>
<param-value>true</param-value>
</context-param>
Ordinarily, Jakarta EE integration module will remove URL session tracking mode from the configuration, overriding any other session tracking configuration specified in web.xml. This is to align with good security practices. If URL tracking mode is desired (such as for testing or historical reasons), add the following to web.xml:
<context-param>
<param-name>org.apache.shiro.ee.enable-url-session-tracking</param-name>
<param-value>true</param-value>
</context-param>
If no manipulation of session tracking modes is desired at all, add the following to web.xml:
<context-param>
<param-name>org.apache.shiro.ee.session-tracking-configuration.disabled</param-name>
<param-value>true</param-value>
</context-param>
Shiro Jakarta EE filter forces request encoding to UTF-8. This is the desired outcome for most, if not all cases. However, if this is not desired, you can disable it by adding the following parameter to web.xml:
<context-param>
<param-name>org.apache.shiro.ee.disable-character-encoding</param-name>
<param-value>true</param-value>
</context-param>
If you need to set a different encoding than UTF-8, add the following parameter to web.xml:
<context-param>
<param-name>org.apache.shiro.ee.character-encoding</param-name>
<param-value>ISO-8859-2</param-value>
</context-param>
The module finds shiro.ini in the same manner as Web Configuration (WEB-INF/shiro.ini by default). Additionally, configuration is enhanced to merge two separate configuration files:
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:META-INF/shiro.ini, classpath:META-INF/shiro2.ini</param-value>
</context-param>
Only two files are supported. More than two file will result in an error.
Custom class is supported, provided it’s inherited from org.apache.shiro.ee.listeners.IniEnvironment or has the same functionality.
By default, Shiro enforces a specific ssl port number where the requests go to. However, if the application is behind a load balancer or a proxy (such as haproxy or nginx), the ports may be different for different instances.
In this case, port filter can be turned off to allow SSL traffic to go to any port.
To disable port filter, put the following in your shiro.ini:
ssl.enablePortFilter = false
SSL filter is only enabled in Jakarta Faces production mode (default) and is disabled in Development mode. However, if SSL filter always needs to be enabled, put the following into your shiro.ini:
ssl.alwaysEnabled = true
When behind SSL-terminating proxy, Shiro may not be able to determine if SSL was used.
X-Forwarded-Proto header can mitigate this. You can configure your proxy set this header to https to tell Shiro
when SSL is used. In addition, it’s always good practice to rewrite redirect response headers.
Below is a haproxy configuration excerpt:
....
frontend tcp-in
http-request set-header X-Forwarded-Proto https if { ssl_fc }
backend server1
http-response replace-header Location ^http://(.*)$ https://\1
...
As an optimization technique, Shiro tries to issue all redirection according to the X-Forwarded-Proto header. If the header is not present, Shiro will use the current request scheme.
If this behavior is not desired, you can add the following property into web.xml
<context-param>
<param-name>org.apache.shiro.ee.redirect.disabled</param-name>
<param-value>true</param-value>
</context-param>
Below is an example of using a CDI bean and assign its property to a variable in shiro.ini
@Named
@ApplicationScoped
public class MyBean {
public boolean getMyValue() {
return true;
}
}
myBeanInstance = myBean
myVariable = $myBeanInstance.myValue
Use CDI bean that implements CipherKeySupplier interface to create a custom logic for generating the cipher key.
For convenience, String data type is used, If the String that’s returned is null or blank (just spaces), the default cipher key generating mechanism is used.
For example, you can use MicroProfile Config to get the cipher key:
@ApplicationScoped
public class CipherKeySource implements CipherKeySupplier {
@Inject
@ConfigProperty(name = "my.config.source.cipher-key")
String cipherKey;
@Override
public String get() {
return cipherKey;
}
}
Shiro always tries to redirect back to a previous page when a login or logout flow was successful.
However, in some cases this may not be desired, such as when the previous page was a login page itself.
In such cases, a fallback page is provided in shiro.ini (usually index or root page), and it is used
even if the previous page is available. Logic is provided by implementing the FallbackPredicate interface.
Here we use the path check. If previous page is part of the auth folder, fallback path (index / root) page will always be used:
@Named
@ApplicationScoped
public class UseFallback implements FallbackPredicate {
@Override
public boolean useFallback(String path, HttpServletRequest request) {
return path.contains("shiro/auth/");
}
}
fallbackType = useFallback
authc.loginFallbackType = $fallbackType
authc.logoutFallbackType = $fallbackType
Jakarta EE module will automatically resubmit forms when session expires and a subsequent re-login occurs. This will prevent users data from loss due to sessions timing out.
web.xml:<context-param>
<param-name>org.apache.shiro.form-resubmit.disabled</param-name>
<param-value>true</param-value>
</context-param>
During form resubmissions, the original request is replayed, and the response is relayed back to the browser, along with any cookies generated. Cookies are set to be secure by default.
web.xml:<context-param>
<param-name>org.apache.shiro.form-resubmit.secure-cookies</param-name>
<param-value>false</param-value>
</context-param>
Alternatively, you can set org.apache.shiro.form-resubmit.secure-cookies system property in the same manner as above.
By default, form resubmission logic replays the request to the original URI. This works for most cases, but in some deployments, such as certain Docker or Kubernetes, host, port or both need to be modified during resubmission. There are two system properties to allow this: org.apache.shiro.form-resubmit-host (String) and org.apache.shiro.form-resubmit-port (Integer).
If Jakarta Faces (JSF) is not available in your environment, you need to put the following into your web.xml to enable proper OmniFaces initialization:
<context-param>
<param-name>org.omnifaces.SKIP_DEPLOYMENT_EXCEPTION</param-name>
<param-value>true</param-value>
</context-param>
Below is an example of Shiro configuration in Java code with CDI only (no shiro.ini):
@ApplicationScoped
public class MyBean {
private DefaultSecurityManager securityManager;
void configureSecurityManager(@Observes @Initialized(ApplicationScoped.class) Object nothing) {
var realm = new SimpleAccountRealm();
securityManager = new DefaultSecurityManager(realm);
realm.addAccount("powerful", "awesome", "admin");
realm.addAccount("regular", "meh", "user");
SecurityUtils.setSecurityManager(securityManager);
}
void destroySecurityManager(@Observes @Destroyed(ApplicationScoped.class) Object nothing) {
securityManager.destroy();
SecurityUtils.setSecurityManager(null);
}
}
By default, Shiro will propagate the Subject to java.security.Principal, which may not always be desired. For example, if calling remote EJBs, the container security mechanism might interpret the principal and will error the remote EJB call as unauthenticated.
To disable this behavior, you can put the following in your web.xml:
<context-param>
<param-name>org.apache.shiro.web.disable-principal</param-name>
<param-value>true</param-value>
</context-param>
The module works transparently to enable Shiro (@RequiresRole) and Jakarta Security (@RolesAllowed) annotations,
without any additional annotations or configuration.
Users get frustrated when they lose data. For example, while filling out a complicated form,
the user get side-tracked with another browser tab or window. Then lunch. After getting back to the form,
they will fill out the rest of the form and submit it. However, since it took a long time, they are now thrown
back to the login screen. Once they log in, all their data entry vanished!
There are few workarounds for his issue, like a periodic ping of the back-end or something similar, but that causes unnecessary load and memory pressure on the server. These methods are also very brittle.
Jakarta EE module will automatically save the form data into Shiro cache when a user is redirected to a login screen.
The cache is encrypted. And when the user subsequently logs back in, the form is automatically submitted and
the data entry is never lost.
Form resubmission works with JSP, Jakarta Faces partial page rendering (Ajax) and with PrimeFaces components.
@SessionScoped and @ViewScoped beansBoth CDI and OmniFaces Session and ViewScoped beans work correctly and transparently with both web container and Shiro native sessions.
When using Shiro with Jakarta Faces, login and logout flow works transparently and correctly without worrying about ViewExpiredException. This works for both Ajax and standard events.
Both server and client state saving methods are supported.
Shiro’s FormAuthenticationFilter (authc by default) in shiro.ini works the same way in Faces
as it does in JSP.
It takes named Faces components and uses them to authenticate.
Below, elements named by id are automatically used to authenticate, and any command button without explicit action will trigger the login.
<h:form prependId="false" id="form">
Username: <h:inputText id="username" p:autofocus="true" title="Username: " required="true" />
Password: <h:inputText id="password" title="Password: " required="true"/>
Remember Me: <h:selectBooleanCheckbox id="rememberMe" title="Remember Me: "/>
<h:commandButton id="login" value="Login ..."/>
</h:form>
Logout can be specified via shiro.ini, without having any additional pages or code:
/shiro/auth/logout* = ssl, logout
<h:outputLink value="#{request.contextPath}/shiro/auth/logout">Logout</h:outputLink>
Below are actions and variables available within Facelets. All actions have zero-argument versions that execute sensible defaults.
<div jsf:rendered="#{authc.sessionExpired}">
Your Session Has Expired
</div>
<div jsf:rendered="#{authc.loginFailure}">
Login Failed
</div>
<h:commandButton value="Login ..." action="#{authc.login}"/>
<h:commandButton value="Login ..." action="#{authc.login(bean.username, bean.password)}"/>
<h:commandButton value="Login ..." action="#{authc.login(bean.username, bean.password, bean.rememberMe)}"/>
<h:commandButton value="Login ..." action="#{authc.redirectIfLoggedIn('page')}"/>
Forms class has external-faces API that can be accessed directly from code. See javadoc for further info.
Jakarta EE module uses Jax-RS module to provide support for non-CDI and non-EJB beans.
See Jax-RS documentation for more details.
Propagation is enabled or disabled for Jax-RS by the Jakarta EE module. See Jax-RS Principal Propagation