一 maven添加jar包依赖
<!-- shiro -->
<!-- shiro整合spring jar包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro的缓存jar包依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro核心jar包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- shiro定时任务 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
二 在web.xml中添加shiroFilter过滤器
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
三 配置spring文件
在applicationContext文件中添加下面这句
<!-- Shiro安全框架产生代理子类的方式: 使用cglib方式,放在事务管理器之前 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
四 配置applicationContext-shiro.xml
将shiro配置单独从applicationContext分离出来,单独配置这个文件,更清爽
<description>apache shiro配置</description>
<!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- 登陆页面配置 -->
<property name="loginUrl" value="/views/login.html"/>
<!-- 登陆成功首页面 -->
<property name="successUrl" value="/views/index.html"/>
<!-- 未经授权的跳转的url -->
<property name="unauthorizedUrl" value="/rest/page/401"/>
<!-- 过滤器链定义 -->
<property name="filterChainDefinitions">
<value>
<!-- anno,任何人都可以访问;authc:必须是登录之后才能进行访问, -->
<!-- 静态资源允许访问 -->
/app/** = anon
/assets/** = anon
/plugins/** = anon
<!-- 登录页允许访问 -->
/rest/user/login = anon
/rest/authz/login = anon
<!-- app接口允许访问 -->
/rest/app/** = anon
<!-- 其他资源需要认证 -->
/** = authc
</value>
</property>
<property name="filters">
<map>
<entry key="authc" value-ref="myFormAuthenticationFilter"/>
</map>
</property>
</bean>
<!--自定义的过滤器 -->
<bean id="myFormAuthenticationFilter" class="com.yufei.core.feature.orm.myFormAuthenticationFilter" />
<!--Shiro正是通过CacheManager组件实现权限数据缓存。
当权限信息存放在数据库中时,对于每次前端的访问请求都需要进行一次数据库查询。
特别是在大量使用shiro的jsp标签的场景下,对应前端的一个页面访问请求会同时出现很多的权限查询操作,
这对于权限信息变化不是很频繁的场景,每次前端页面访问都进行大量的权限数据库查询是非常不经济的。因此,非常有必要对权限数据使用缓存方案。-->
<!-- 缓存管理器 使用Ehcache实现 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
</bean>
<!-- 会话DAO -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO"/>
<!-- 会话管理器 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO" ref="sessionDAO"/>
<!-- 会话超时时间,单位:3600000毫秒 =1小时 -->
<property name="globalSessionTimeout" value="1800000"/>
<!-- 定时清理失效会话, 清理用户直接关闭浏览器造成的孤立会话 -->
<property name="sessionValidationInterval" value="60000"/>
<!-- <property name="sessionValidationSchedulerEnabled" value="false"/> -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
<property name="sessionIdCookieEnabled" value="true"/>
</bean>
<!-- 指定本系统SESSIONID, 默认为: JSESSIONID 问题: 与SERVLET容器名冲突, 如JETTY, TOMCAT 等默认JSESSIONID,
当跳出SHIRO SERVLET时如ERROR-PAGE容器会为JSESSIONID重新分配值导致登录会话丢失! -->
<!--通过配置sessionIdCookie属性,解决被服务器重写cookie中会话ID -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg name="name" value="jeesite.session.id"/>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realms">
<list>
<!-- 自定义的realm -->
<ref bean="securityRealm"/>
</list>
</property>
<!-- cacheManager,集合spring缓存工厂 -->
<property name="cacheManager" ref="shiroEhcacheManager" />
<property name="sessionManager" ref="sessionManager" />
</bean>
<!-- Shiro生命周期处理器 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
</beans>
在上面的配置文件上有几个地方注意:
1.过滤器配置里的自定义的过滤器myFormAuthenticationFilter
public class myFormAuthenticationFilter extends FormAuthenticationFilter {
//过滤器
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
return executeLogin(request, response);
} else {
return true;表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理
}
} else {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
// HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/rest/page/login");
response.getWriter().println("<script>window.open('" + httpServletRequest.getContextPath() + "_ui/views/login.html' , '_top')</script>");
return false;//如果返回false表示该拦截器实例已经处理了,跳转到登录界面。
}
}
}
2.缓存管理器的导入ehcache-shiro.xml
<ehcache updateCheck="false" name="shiroCache">
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
3.自定义的securityRealm
@Component(value = "securityRealm")
public class SecurityRealm extends AuthorizingRealm {
@Resource
private UserService userService;
@Resource
private RoleService roleService;
/**
* 权限检查
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//principals:身份,即主体的标识属性,
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
String username = String.valueOf(principals.getPrimaryPrincipal());//一个主体可以有多个属性但只有一个Primary principals,这里指的是用户名
MySysDeptPerson user = null;
try {
user = userService.selectByUsername(username);//通过获取的username查询数据库中是否有对应的用户名
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (user == null){
return null;
}
//如果用户名不为空,则通过用户id查询出用户角色
final List<SysRole> roleInfos = roleService.selectRolesByUserId(user.getPersonId());
for (SysRole role : roleInfos) {
// 添加角色
System.err.println(role);
authorizationInfo.addRole(role.getRoleCode());
}
return authorizationInfo;//返回权限信息
}
/**
* 登录验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token:登录令牌
String username = String.valueOf(token.getPrincipal());//获取用户名
String password = new String((char[]) token.getCredentials());//获取密码
// 通过数据库进行验证
final SysPerson authentication = userService.authentication(new SysPerson(username, password));
if (authentication == null) {
throw new AuthenticationException("用户名或密码错误.");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, getName());//getName指的是当前realm的名称
return authenticationInfo;
}
}
五 在web.xml引入,项目启动部署部署
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:applicationContext.xml
classpath*:applicationContext-shiro.xml
</param-value>
</context-param>
上面部署工作完成,开始写自己的代码
controller层
/**
* 用户【登录】
* @param user
* @param result
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
/*
*@Valid 数据校验
*BindingResult 错误信息 跟@Valid一起
*/
public String login(@Valid SysPerson user, String sessionTimeout, BindingResult result, Model model, HttpServletRequest request) throws Exception {
try {
// 获取登录信息
Subject subject = SecurityUtils.getSubject();
// 已登陆则 跳到首页
if (subject.isAuthenticated()) {
//一个被验证的Subject是成功验证后(如登录成功)并存于当前session中,一个被认为验证过的对象调用subject.isAuthenticated()将返回真。
return "index";
}
// 参数错误
if (result.hasErrors()) {
model.addAttribute("error", "参数错误!");
return "login";
}
// 参数未传
if (StringUtils.isBlank(user.getUserName()) && StringUtils.isBlank(user.getPassword())) {
return "login";// 跳回登录页
}
// 1.身份验证
subject.login(new UsernamePasswordToken(user.getUserName(), user.getPassword()));
// 验证成功在Session中保存用户信息
final MySysDeptPerson authUserInfo = userService.selectByUsername(user.getUserName());
// 2.如果登录的是内置用户则看到的是所有单位和部门,非内置用户需要根据权限筛选
if (authUserInfo.getIsinner() == AdminUserType.NOTINNERUSER.getValue()) {
/**
* 这里面的根据自己的需求来写,我就不放上去了
*/
}
// 3.在Session中保存用户、角色信息
List<SysRole> roleList = roleService.selectRolesByUserId(authUserInfo.getPersonId());
request.getSession().setAttribute("userInfo", authUserInfo);
request.getSession().setAttribute("roleList", roleList);
// 4.是否超时
if (sessionTimeout != null) {
SecurityUtils.getSubject().getSession().setTimeout(Long.parseLong(sessionTimeout));
}
// 5.记录到操作日志
this.saveOperatorLog("首页", "用户登陆:" + authUserInfo.getName());//这个方法是继承BaseController中的方法
} catch (AuthenticationException e) {
// 身份验证失败
model.addAttribute("error", "用户名或密码错误 !");
return "login";
}
return "redirect:/";
}
/**
* 用户【登出】
* @param session
* @return
*/
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpSession session) {
session.removeAttribute("userInfo");//移除session中的用户信息
// 登出操作
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}