Fix broken login flow in shiro-basic example (#545)

The example was unusable: every request redirected to Shiro's default
loginUrl of /login.jsp, which does not exist here, producing an infinite
redirect loop. shiro.ini defined only [users] and [roles], so no filter
chain was configured and Shiro protected every path including the login
page and the form it posts to. Because shiroFilter is mapped for FORWARD
as well as REQUEST, the JSPs the Struts actions forward to were caught
too, so listing only the actions would not have been enough.

Adds a [main] section pointing authc at the login action, and a [urls]
chain leaving the login path anonymous while protecting the rest.

Separately, the container rewrote redirect URLs as ...;jsessionid=... on
a visitor's first request, and Jetty 11 rejects its own rewritten URI
with HTTP 400 Invalid request. Restricting session tracking to cookies
stops the rewriting.

Verified in a browser and over HTTP: login as lonestarr renders the
welcome page with roles and permissions resolved, logout returns to the
login page, and welcome.action is no longer reachable afterwards.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
diff --git a/shiro-basic/src/main/resources/shiro.ini b/shiro-basic/src/main/resources/shiro.ini
index 436c465..dec7d78 100644
--- a/shiro-basic/src/main/resources/shiro.ini
+++ b/shiro-basic/src/main/resources/shiro.ini
@@ -5,6 +5,17 @@
 # =============================================================================
 
 # -----------------------------------------------------------------------------
+# Shiro objects and settings
+#
+# This example authenticates programmatically in LoginAction rather than letting
+# Shiro's form filter do it, so authc only needs to know where to send an
+# unauthenticated visitor. Shiro's default is /login.jsp, which does not exist
+# here — the login form is rendered by the "login" Struts action.
+# -----------------------------------------------------------------------------
+[main]
+authc.loginUrl = /login.action
+
+# -----------------------------------------------------------------------------
 # Users and their (optional) assigned roles
 # username = password, role1, role2, ..., roleN
 # -----------------------------------------------------------------------------
@@ -22,4 +33,19 @@
 [roles]
 admin = *
 schwartz = lightsaber:*
-goodguy = winnebago:drive:eagle5
\ No newline at end of file
+goodguy = winnebago:drive:eagle5
+
+# -----------------------------------------------------------------------------
+# Filter chain, evaluated top-down — the first matching path wins.
+#
+# Without this section Shiro protects every path, including the login page and
+# the form it posts to, which leaves an unauthenticated visitor in a redirect
+# loop. Note that shiroFilter is mapped for FORWARD as well as REQUEST, so the
+# JSPs the Struts actions forward to must be listed too, not just the actions.
+# -----------------------------------------------------------------------------
+[urls]
+/index.jsp = anon
+/login.action = anon
+/authuser.action = anon
+/pages/login.jsp = anon
+/** = authc
\ No newline at end of file
diff --git a/shiro-basic/src/main/webapp/WEB-INF/web.xml b/shiro-basic/src/main/webapp/WEB-INF/web.xml
index 7cfbe3e..4bad1e4 100644
--- a/shiro-basic/src/main/webapp/WEB-INF/web.xml
+++ b/shiro-basic/src/main/webapp/WEB-INF/web.xml
@@ -4,6 +4,14 @@
          xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
          version="6.0">
   <display-name>struts2shiro</display-name>
+
+  <!-- Track sessions with a cookie only. Without this the container rewrites
+       redirect URLs as ...;jsessionid=... on a visitor's first request, and
+       Jetty 11 rejects its own rewritten URI with HTTP 400 Invalid request. -->
+  <session-config>
+    <tracking-mode>COOKIE</tracking-mode>
+  </session-config>
+
   <listener>
     <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
   </listener>