最近做的项目,由于客户需求,登录的用户信息必须经过身份验证,自己研究了一下,写个博客与大家分享一下我的心得。本次项目用到了shiro的登录,记住我,前后台分离功能。这里只做了登录认证,没有权限管理。
项目构建:
在pom.xml文件:
org.apache.shiro
shiro-spring
1.2.3
org.apache.shiro
shiro-web
1.2.3
org.apache.shiro
shiro-ehcache
1.2.3
org.apache.shiro
shiro-cas
1.2.3
在 web.xml文件里:
contextConfigLocation
classpath:applicationContext.xml,classpath:shiro/spring-context-shiro.xml
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
shiroFilter
/*
shrio配置文件 spring-context-shiro:<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"
default-lazy-init="true">
Shiro Configuration
/userMember/login = authc
/logout = logout
/apply/** = user
/alter/** = user
class="包路径.FormAuthenticationFilter">
class="包路径.SystemAuthorizingRealm" />
class="包路径.UserAuthorizingRealm" />
class="包路径.SystemAuthorizingRealm">
class="包路径.UserAuthorizingRealm">
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
class="org.apache.shiro.web.mgt.CookieRememberMeManager">
value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
在这里有一个 logintype,这个变量是我,是我自定义的,本身的shrio是没有,是做前后台登录的标志位。
FormAuthenticationFilter 类:package 包路径;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FormAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
/**
* logger日志
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 序列化id
*/
private static final long serialVersionUID = -2271706136984114038L;
/**
* 登录类型
*/
public static final String DEFAULT_LOGINTYPE_PARAM = "logintype";
private String logintypeParam = DEFAULT_LOGINTYPE_PARAM;
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
String logintype = getLogintype(request);
logger.debug("createToken username:{},password:{},loginType:{"+logintype+"} ...",username,password);
if (password==null){
password = "";
}
String host = getRemoteAddr((HttpServletRequest)request);
boolean rememberMe = isRememberMe(request);
return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, logintype);
}
public void setLogintypeParam(String logintypeParam) {
this.logintypeParam = logintypeParam;
}
public String getLogintypeParam() {
return logintypeParam;
}
protected String getLogintype(ServletRequest request) {
return WebUtils.getCleanParam(request, getLogintypeParam());
}
/**
* 获得用户远程地址
*/
public static String getRemoteAddr(HttpServletRequest request){
String remoteAddr = request.getHeader("X-Real-IP");
if (StringUtils.isNotBlank(remoteAddr)) {
remoteAddr = request.getHeader("X-Forwarded-For");
}else if (StringUtils.isNotBlank(remoteAddr)) {
remoteAddr = request.getHeader("Proxy-Client-IP");
}else if (StringUtils.isNotBlank(remoteAddr)) {
remoteAddr = request.getHeader("WL-Proxy-Client-IP");
}
return remoteAddr != null ? remoteAddr : request.getRemoteAddr();
}
}
UsernamePasswordToken 类:package 包路径;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UsernamePasswordToken extends org.apache.shiro.authc.UsernamePasswordToken {
/**
* logger日志
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 序列化id
*/
private static final long serialVersionUID = -2271706136984114038L;
/**
* 登录类型
*/
private String logintype ;
public String getLogintype() {
return logintype;
}
public void setLogintype(String logintype) {
this.logintype = logintype;
}
public UsernamePasswordToken (String username, char[] password,
boolean rememberMe, String host, String logintype) {
super(username, password, rememberMe, host);
this.logintype = logintype;
}
}
DefautModularRealm 类,多个realm集中管理:package 包路径;
import java.util.Collection;
import java.util.Map;
import org.apache.shiro.ShiroException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.util.CollectionUtils;
import 系统文件路径.SysConstant;
public class DefautModularRealm extends org.apache.shiro.authc.pam.ModularRealmAuthenticator{
private Map definedRealms;
/**
* 多个realm实现
*/
@Override
protected AuthenticationInfo doMultiRealmAuthentication(Collection realms, AuthenticationToken token) {
return super.doMultiRealmAuthentication(realms, token);
}
/**
* 调用单个realm执行操作
*/
@Override
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,AuthenticationToken token) {
// 如果该realms不支持(不能验证)当前token
if (!realm.supports(token)) {
throw new ShiroException("token错误!");
}
AuthenticationInfo info = null;
try {
info = realm.getAuthenticationInfo(token);
if (info == null) {
throw new ShiroException("token不存在!");
}
} catch (Exception e) {
throw new ShiroException("用户名或者密码错误!");
}
return info;
}
/**
* 判断登录类型执行操作
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {
this.assertRealmsConfigured();
Realm realm = null;
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//判断是否是后台用户
if (token.getLogintype().equals(SysConstant.FRONT_LOGIN)) {
realm = (Realm) this.definedRealms.get("systemAuthorizingRealm");
}
else if (token.getLogintype().equals(SysConstant.ADMIN_LOGIN)){
realm = (Realm) this.definedRealms.get("userAuthorizingRealm");
}
return this.doSingleRealmAuthentication(realm, authenticationToken);
}
/**
* 判断realm是否为空
*/
@Override
protected void assertRealmsConfigured() throws IllegalStateException {
this.definedRealms = this.getDefinedRealms();
if (CollectionUtils.isEmpty(this.definedRealms)) {
throw new ShiroException("值传递错误!");
}
}
public Map getDefinedRealms() {
return this.definedRealms;
}
public void setDefinedRealms(Map definedRealms) {
this.definedRealms = definedRealms;
}
}
SystemAuthorizingRealm 后台认证realmpackage 包路径;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import 系统文件路径.ENUM_SYSTEM;
import 系统文件路径.重写后台用户实体类.Principal;
import 系统文件路径.后台用户实体类.UserMemberDO;
import 系统文件路径.后台用户服务.UserMemberService;
@Service
public class SystemAuthorizingRealm extends AuthorizingRealm {
/**
* logger日志
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 序列化id
*/
private static final long serialVersionUID = -2271706136984114038L;
/**
* userMemberServicef服务
*/
@Resource
private UserMemberService userMemberService;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("sys:manager");
info.addStringPermission("user");
System.out.println("开始授权");
return info;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
UserMemberDO user = userMemberService.getByName(token.getUsername());
if (user == null ) {
throw new UnknownAccountException("用户名不存在");
} else if (ENUM_SYSTEM.ACCOUNT_VERIFICATION_NO.getKey().equals(user.getStatus())) {
throw new DisabledAccountException("账户未审核通过!");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(new Principal(user), user.getPassword(), null,
getName());
return authenticationInfo;
}
/**
* 设定密码校验的MD5算法与迭代次数
*/
@PostConstruct
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("MD5");
setCredentialsMatcher(matcher);
}
@Override
protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
protected void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
protected void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
public void clearAllCache() {
clearAllCachedAuthorizationInfo();
clearAllCachedAuthenticationInfo();
}
}
UserAuthorizingRealm 前台用户认证realmpackage 包路径.shiro;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import 系统路径.ENUM_SYSTEM;
import 系统路径.重写前台用户实体类.user.Principal;
import 系统路径.前台用户实体类.user.UserDO;
import 系统路径.前台用户服务.user.UserService;
@Service
public class UserAuthorizingRealm extends AuthorizingRealm {
/**
* logger日志
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 序列化id
*/
private static final long serialVersionUID = -2271706136984114038L;
/**
* userMemberServicef服务
*/
@Resource
private UserService userService;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission("sys:manager");
info.addStringPermission("user");
System.out.println("开始授权");
return info;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
System.out.println(token.getUsername());
UserDO user = userService.getByName(token.getUsername());
if (user == null ) {
throw new UnknownAccountException("用户名不存在");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(new Principal(user), user.getPassword(), null,
getName());
return authenticationInfo;
}
/**
* 设定密码校验的MD5算法与迭代次数
*/
@PostConstruct
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher("MD5");
setCredentialsMatcher(matcher);
}
@Override
protected void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
@Override
protected void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
@Override
protected void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
public void clearAllCache() {
clearAllCachedAuthorizationInfo();
clearAllCachedAuthenticationInfo();
}
}
在这里有一个这样的一个类Principal这个类的作用主要是在用户登录的时候shiro保存登录信息,包括id ,姓名,密码,状态。等等。可以自定义。在JSP标签里的输出对象,在下面有讲。
前台登录controller:package 系统路径.controller.usermember;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.alibaba.fastjson.JSON;
import 系统路径.前台用户实体类.Principal;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Controller
@RequestMapping("userMember")
public class UserMemberController extends BaseController {
/**
* logger日志
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 序列化id
*/
private static final long serialVersionUID = -2271706136984114038L;
/**
* 登录
* @param request 请求
* @param response 响应
* @param member 前台用户对象
* @param model 模板
* @return String 页面
*/
@RequestMapping(value = "/login")
public String login(HttpServletRequest request, HttpServletResponse response,
@ModelAttribute("member") UserMemberDO member, ModelMap model) {
logger.info("login...");
Subject subject = SecurityUtils.getSubject();
Principal principal = (Principal) subject.getPrincipal();
// 如果已经登录,则跳转到管理首页
if (principal != null) {
return "redirect:/home/forward"; // 首页
}
String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
String errorMsg = null;
if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
logger.info("该用户名不存在");
errorMsg = "该用户名不存在";
} else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
logger.info("密码错误");
errorMsg = "密码错误";
} else if (DisabledAccountException.class.getName().equals(exceptionClassName)) {
logger.info("该账号审核未通过");
errorMsg = "该账号审核未通过";
} else if (exceptionClassName != null) {
logger.info("登录错误" + exceptionClassName);
errorMsg = "登录错误";
}
model.addAttribute("errorMsg", errorMsg);
return PAGE_LOGIN;
}
}
这里我只写出前台登录的controller,后台的登录controller一样就不贴出了。
在JSP页面:
pageEncoding="UTF-8"%>
登录登录成功以后,如何显示的登录者的用户名。在这里shrio有自己的页面标签:
首先导入 标签:
登录|
这里详细的就不贴出了,也就不详细说明了。只是告诉大家,有这样一个途径,就不用费尽心思的还要把信息封装到session 里面了。一开始,我不知道有这样的途径,所以,把信息封装到了session里面了,这真的是多此一举。