Shiro-认证思路分析及认证流程实现

本文详细介绍了如何在SpringMVC项目中整合Shiro进行权限管理,包括配置web.xml和spring配置文件,实现登录验证和权限控制,以及自定义Realm与缓存管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、获取当前的Subject。调用SecurityUtils.getSubject();
2、测试当前的用户是否已经被认证。即是否已经登录。调用Subject的isAuthenticated()
3、若没有被认证,则把用户名和密码封装为UsernamePasswordToken对象
    1)创建一个表单页面
    2)把请求提交到SpringMVC的Handler
    3)获取用户名和密码
4、执行登录:调用Subject的login(AuthenticationToken)方法。
5、自定义Realm的方法,从数据库中获取对应的记录,返回给Shiro。
    1)实际上需要继承org.apache.shiro.realm.AuthenticatingRealm类,org.apache.shiro.realm.AuthenticatingRealm间接实现了Realm接口
    2)实现doGetAuthenticationInfo(AuthenticationToken)方法。
6、由shiro完成对密码的比对。

项目结构

1、整合spirng和shiro

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	version="2.5">

	<!-- needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>

	<!-- Bootstraps the root web application context before servlet initialization -->
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>

	<!-- 配置shiro的过滤器 -->
	<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<init-param>
			<param-name>targetFilterLifecycle</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>shiroFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


	<!-- The front controller of this Spring Web application, responsible for 
		handling all application requests -->
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

</web-app>

spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


	<!-- 配置SecurityManager -->
	<bean id="securityManager"
		class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="cacheManager" ref="cacheManager" />
		<!-- Single realm app. If you have multiple realms, use the 'realms' property 
			instead. -->
		<property name="sessionMode" value="native" />
		<property name="realm" ref="jdbcRealm" />
	</bean>

	<!-- 配置cacheManager
			需要加入ehcache的配置文件和jar包
	 -->
	<bean id="cacheManager"	class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
	</bean>

	<!-- Used by the SecurityManager to access security data (users, roles, 
		etc). Many other realm implementations can be used too (PropertiesRealm, 
		LdapRealm, etc. -->
	<!-- 直接使用实现了Realm接口的bean -->
	<bean id="jdbcRealm" class="com.atguigu.shiro.realms.ShiroRealm"></bean>

	<!-- ========================================================= Shiro Spring-specific integration ========================================================= -->
	<!-- Post processor that automatically invokes init() and destroy() methods 
		for Spring-configured Shiro objects so you don't have to 1) specify an init-method 
		and destroy-method attributes for every bean definition and 2) even know 
		which Shiro objects require these methods to be called. -->
	<!-- 配置生命周期的后置处理器,自动调用spirng的IOC容器中shiro的方法 -->
	<bean id="lifecycleBeanPostProcessor"
		class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

	<!-- Enable Shiro Annotations for Spring-configured beans. Only run after 
		the lifecycleBeanProcessor has run: 
		启用shiro的生命周期注解,其依赖lifecycleBeanPostProcessor,所以在配置了lifecycleBeanPostProcessor之后才能生效	
	-->
	<bean
		class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
		depends-on="lifecycleBeanPostProcessor" />
	<bean
		class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager" />
	</bean>

	<!--
		这里的id必须于web.xml文件中配置的DelegatingFilterProxy的filter-name一样
		若不一样则抛出NoSuchBeanDefinitionException 
		因为shiro会到spring的IOC容器中找shiroFilter对应的bean
		
		若不一致的话也可以在fileter的初始化参数中配置targetBeanName,将这里的shiroFilter换成targetBeanName的值就可以了
	-->
	<bean id="shiroFilter"
		class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<property name="loginUrl" value="/login.jsp" />
		<property name="successUrl" value="/list.jsp" />
		<property name="unauthorizedUrl" value="/unauthorized.jsp" />
		<!-- 
			配置哪些页面需要保护
			以及访问页面的权限
			拦截器:(这里的url支持Ant风格模式)
			1).anon 可以被匿名访问
			2) .authc 需要认证才能访问
			这里的url优先匹配
		 -->
		<property name="filterChainDefinitions">
			<value>
				/login.jsp = anon
				/shiro/login = anon
				
				# everything else requires authentication:
				/** = authc
				
				/list.jsp = anon
			</value>
		</property>
	</bean>


</beans>

ehcache.xml

<ehcache>

    <!-- Sets the path to the directory where cache .data files are created.

         If the path is a Java System Property it is replaced by
         its value in the running VM.

         The following properties are translated:
         user.home - User's home directory
         user.dir - User's current working directory
         java.io.tmpdir - Default temp file path -->
    <diskStore path="java.io.tmpdir"/>


    <!--Default Cache configuration. These will applied to caches programmatically created through
        the CacheManager.

        The following attributes are required for defaultCache:

        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        />

    <!--Predefined caches.  Add your cache configuration settings here.
        If you do not have a configuration for your cache a WARNING will be issued when the
        CacheManager starts

        The following attributes are required for defaultCache:

        name              - Sets the name of the cache. This is used to identify the cache. It must be unique.
        maxInMemory       - Sets the maximum number of objects that will be created in memory
        eternal           - Sets whether elements are eternal. If eternal,  timeouts are ignored and the element
                            is never expired.
        timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
                            if the element is not eternal. Idle time is now - last accessed time
        timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
                            if the element is not eternal. TTL is now - creation time
        overflowToDisk    - Sets whether elements can overflow to disk when the in-memory cache
                            has reached the maxInMemory limit.

        -->

    <!-- Sample cache named sampleCache1
        This cache contains a maximum in memory of 10000 elements, and will expire
        an element if it is idle for more than 5 minutes and lives for more than
        10 minutes.

        If there are more than 10000 elements it will overflow to the
        disk cache, which in this configuration will go to wherever java.io.tmp is
        defined on your system. On a standard Linux system this will be /tmp"
        -->
    <cache name="sampleCache1"
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="true"
        />

    <!-- Sample cache named sampleCache2
        This cache contains 1000 elements. Elements will always be held in memory.
        They are not expired. -->
    <cache name="sampleCache2"
        maxElementsInMemory="1000"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> -->

    <!-- Place configuration for your caches following -->

</ehcache>

2、编码实现

login.jsp

	<form action="shiro/login" method="post">
		UserName:<input name="username" type="text"/>
		
		<br><br>
		
		PassWord:<input name="password" type="password"/>
		
		<br><br>
		
		<input type="submit" value="Submit"/>
	</form>

ShiroHandler.java

package com.atguigu.shiro.handlers;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 实现Shiro认证
 * @author Lee
 *
 */
@Controller
@RequestMapping("/shiro")
public class ShiroHandler {

	@RequestMapping(value="/login",method=RequestMethod.POST)
	public String login(@RequestParam("username") String username,@RequestParam("password") String password) {
		System.out.println(username + "" + password);
		Subject currentUser = SecurityUtils.getSubject();
		
		if (!currentUser.isAuthenticated()) {
			UsernamePasswordToken token = new UsernamePasswordToken(username, password);
			token.setRememberMe(true);
			System.out.println(token.hashCode());
			try {
				currentUser.login(token);
			} catch (AuthenticationException e) {
				System.out.println("登陆失败");
				e.printStackTrace();
			}
		}
		return "redirect:/list.jsp";
	}
	
}

ShiroRealm.java

package com.atguigu.shiro.realms;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.realm.AuthenticatingRealm;

/**
 * 
 * @author Lee
 *
 */
public class ShiroRealm extends AuthenticatingRealm{

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		System.out.println("doGetAuthenticationInfo" + token);
		System.out.println(token.hashCode());
		return null;
	}

}

 

<think>嗯,用户想了解在前后端分离架构下使用Shiro进行认证的具体实现方案。从提供的引用信息来看,用户应该是在SpringBoot+Vue技术栈下遇到了Shiro认证问题。用户可能正在开发一个前后端分离的管理系统,遇到了跨域导致的会话不一致问题。引用[2]中提到的问题描述很典型——每次请求都被视为新会话,导致认证失败。用户的核心诉求应该是找到成熟的Shiro无状态认证方案。我之前回复的方案已经覆盖了核心思路:自定义Realm实现凭证校验,用JWT替代Session,自定义过滤器处理Token。不过用户提供的引用里还透露了几个关键点需要补充:一是Shiro异常处理(引用[1]),二是集群部署时的Session共享方案(引用[3]中提到用Redis做Session共享),这些在无状态方案里都需要相应调整。特别要注意引用[2]中网友的实践经验——直接把Shiro的SessionID传给前端是可行的过渡方案,但不是最佳实践。更规范的做法应该是完全去Session化,这也是我推荐JWT方案的原因。不过如果用户系统需要兼容原有架构,混合方案里的Cookie+SessionID方式也值得说明。用户可能还隐含了性能方面的担忧,毕竟引用里提到“登录时限”“单点登录”等未完善功能。在方案优化部分需要强调Token过期时间和续签机制,这也是很多开源方案容易忽略的点。从技术细节看,示例代码中的createToken方法可能需要补充RefreshToken的生成逻辑,分布式环境下还要考虑Token的黑名单管理。不过用户当前提问聚焦在基础认证实现,这些高级特性可以先点到为止。</think> 在前后端分离架构下使用Shiro实现认证,核心在于**用无状态Token替代Session管理**。以下是具体实现方案(基于SpringBoot+Vue技术栈): --- ### 一、核心流程 ```mermaid sequenceDiagram 前端->>+后端: 发送登录请求(username+password) 后端->>+Shiro: 调用Subject.login() Shiro->>+自定义Realm: 执行认证逻辑 自定义Realm-->>-Shiro: 返回Account对象 后端-->>-前端: 生成JWT返回 前端->>后端: 后续请求Header携带Token 后端->>Shiro: 通过JWTFilter验证Token Shiro-->>后端: 构建认证信息 后端-->>前端: 返回业务数据 ``` --- ### 二、关键实现步骤 #### 1. 自定义Realm ```java public class JwtRealm extends AuthorizingRealm { @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; // 支持自定义Token类型 } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { String jwt = (String) token.getCredentials(); // 1. 解析JWT获取用户ID String userId = JwtUtils.parseUserId(jwt); // 2. 查询数据库用户信息 User user = userService.findById(userId); // 3. 校验Token有效性(如过期时间、黑名单等) if(!JwtUtils.verify(jwt) || isTokenBlocked(jwt)) { throw new AuthenticationException("Token无效"); } return new SimpleAuthenticationInfo(user, jwt, getName()); } // 授权逻辑省略... } ``` #### 2. 自定义Token过滤器 ```java public class JwtFilter extends AuthenticatingFilter { @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { HttpServletRequest req = (HttpServletRequest) request; // 从Header获取Token String token = req.getHeader("Authorization"); return new JwtToken(token); } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // 1. 尝试执行登录认证 if (executeLogin(request, response)) { return true; } // 2. Token无效时返回401 HttpServletResponse res = (HttpServletResponse) response; res.setStatus(HttpStatus.UNAUTHORIZED.value()); res.getWriter().write("{\"code\":401,\"msg\":\"未认证\"}"); return false; } } ``` #### 3. Shiro配置类 ```java @Configuration public class ShiroConfig { @Bean public DefaultWebSecurityManager securityManager(JwtRealm realm) { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(realm); // 禁用Session存储 manager.setSessionStorageEvaluator(session -> false); return manager; } @Bean public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean(); factory.setSecurityManager(securityManager); // 添加JWT过滤器 Map<String, Filter> filters = new HashMap<>(); filters.put("jwt", new JwtFilter()); factory.setFilters(filters); // 设置过滤规则 Map<String, String> filterMap = new LinkedHashMap<>(); filterMap.put("/login", "anon"); // 登录接口放行 filterMap.put("/**", "jwt"); // 其他请求需认证 factory.setFilterChainDefinitionMap(filterMap); return factory; } } ``` #### 4. 登录接口示例 ```java @RestController public class LoginController { @PostMapping("/login") public ResponseEntity<Map<String, String>> login(@RequestBody LoginDto dto) { // 1. Shiro执行认证 Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(dto.getUsername(), dto.getPassword())); // 2. 认证成功生成JWT User user = (User) subject.getPrincipal(); String token = JwtUtils.generateToken(user.getId()); // 3. 返回Token给前端 return ResponseEntity.ok().header("Authorization", token) .body(Collections.singletonMap("token", token)); } } ``` --- ### 三、前端处理(Vue示例) ```javascript // 1. 登录后存储Token axios.post('/login', {username, password}).then(res => { localStorage.setItem('token', res.data.token) }) // 2. 请求拦截器添加Token axios.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) config.headers.Authorization = token return config }) // 3. 响应拦截器处理401错误 axios.interceptors.response.use(null, error => { if (error.response.status === 401) { router.push('/login') // 跳转登录页 } }) ``` --- ### 四、优化建议 1. **Token刷新机制**:添加refreshToken实现无感续期 2. **并发控制**:使用Redis存储用户最新Token,防止多终端登录冲突 3. **安全加固**: - 设置合理的Token过期时间(如accessToken: 2小时) - 启用HTTPS防止Token泄露 - 使用HttpOnly Cookie存储Token防XSS攻击 4. **跨域支持**:配置`CorsFilter`确保跨域请求携带Header > **关键点**:前后端分离的核心在于**完全解耦Session依赖**,通过Token传输认证状态。官方文档建议配合`shiro-redis`实现分布式会话管理[^2],若采用无状态方案则需确保Token具备自包含性且可验证[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值