1.XML配置
/static/** = anon
/error/** = anon
/images/kaptcha.do* = anon
/login = authc
/login* = authc
/smart/** = authc
/customLogin = authcstom
/customLogin* = authcstom
/custom/** = authcstom
/** = role[*]
2.两个realm
public class CustomRealm extends AuthorizingRealm {
protected AccountService accountService;
/**
* 认证回调函数,登录时调用.
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
CaptchaUsernamePasswordToken token = (CaptchaUsernamePasswordToken) authcToken;
doCaptchaValidate(token);
User user = accountService.findUserByLoginName(token.getUsername());
if (user != null) {
//2.判断是否有区,去掉该区的市、省编码
if(StringUtils.isNotBlank(user.getRegion())){
String[] resions = user.getRegion().split(",");
Set resionsSet = new HashSet();
CollectionUtils.addAll(resionsSet, resions);
resionsSet.remove("");
if(StringUtils.isNotBlank(user.getCity())){
String[] _citys = user.getCity().split(",");
Set _citysSet = new HashSet();
CollectionUtils.addAll(_citysSet, _citys);
_citysSet.remove("");
//循环遍历区 去掉多余市编码
for (String resion : resionsSet) {
String _resion = resion.substring(0,resion.length()-2)+"00";
_citysSet.remove(_resion);
}
//重新保存
user.setCity(StringUtils.join(_citysSet.toArray(),","));
}
user.setRegion(StringUtils.join(resionsSet.toArray(),","));
}
//处理省多余的逗号
if(StringUtils.isNotBlank(user.getProvince())){
String[] provinces = user.getProvince().split(",");
Set provinceSet = new HashSet();
CollectionUtils.addAll(provinceSet,provinces);
provinceSet.remove("");
user.setProvince(StringUtils.join(provinceSet.toArray(),","));
}
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userInfo", user);
byte[] salt = Encodes.decodeHex(user.getSalt());
return new SimpleAuthenticationInfo(new ShiroUser(user.getId(),user.getLoginName(), user.getName()), user.getPassword(),ByteSource.Util.bytes(salt), getName());
}else{
throw new UnknownAccountException();
}
}
/**
* 验证码校验
*
* @param token
*/
protected void doCaptchaValidate(CaptchaUsernamePasswordToken token) {
String captcha = (String) SecurityUtils
.getSubject()
.getSession()
.getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);
if (captcha != null && !captcha.equalsIgnoreCase(token.getCaptcha())) {
throw new IncorrectCaptchaException("验证码错误");
}
}
/**
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 基于Permission的权限信息
List pls = accountService.findUserPermission(shiroUser.id);
for (Permission p : pls) {
info.addStringPermission(p.getPerm());
}
// 基于Role的权限信息
List rls = accountService.getRoleByUserId(shiroUser.id);
for(Role r : rls){
info.addRole(r.getName());
}
return info;
}
/**
* 设定Password校验的Hash算法与迭代次数.
*/
@PostConstruct
public void initCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(
AccountService.HASH_ALGORITHM);
matcher.setHashIterations(AccountService.HASH_INTERATIONS);
setCredentialsMatcher(matcher);
}
@Autowired
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
/**
* 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息.
*/
public static class ShiroUser implements Serializable {
private static final long serialVersionUID = -1373760761780840081L;
public Long id;
public String loginName;
public String name;
public ShiroUser(Long id, String loginName, String name) {
this.id = id;
this.loginName = loginName;
this.name = name;
}
public String getName() {
return name;
}
public String getLoginName() {
return loginName;
}
/**
* 本函数输出将作为默认的输出.
*/
@Override
public String toString() {
return loginName;
}
/**
* 重载hashCode,只计算loginName;
*/
@Override
public int hashCode() {
return Objects.hashCode(loginName);
}
/**
* 重载equals,只计算loginName;
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ShiroUser other = (ShiroUser) obj;
if (loginName == null) {
if (other.loginName != null)
return false;
} else if (!loginName.equals(other.loginName))
return false;
return true;
}
}
}
empty
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
// 日志组件
private Logger LOG = LoggerFactory.getLogger(CustomFormAuthenticationFilter.class);
public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
private String captchaParam = DEFAULT_CAPTCHA_PARAM;
public static final String LOGIN_TYPE = LoginType.CUSTOM.toString();
private int maxSessions = 1;
private boolean errorIfMaximumExceeded = false;
private static final OnceLoginSessionMgr onceLoginSessionMgr = OnceLoginSessionMgr.getInstance();
/**
* fix relogin
* 拒绝二次登录或.
*/
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
if(errorIfMaximumExceeded) {
return rejectSecondLogin(token, subject, request, response);
} else {
return invalidBeforeLogin(token, subject, request, response);
}
}
/**
* fix relogin
* 拒绝二次登录.
*/
private boolean rejectSecondLogin(AuthenticationToken token, Subject subject, ServletRequest request,ServletResponse response) throws Exception {
String username = getUsername(request);
if (onceLoginSessionMgr.getShiroSubject(username) != null) {
/*SecurityUtils.getSubject().logout();
throw new ReLoginException("账号已经登录,请勿重复登录");*/
}
onceLoginSessionMgr.addShiroSubject(username, SecurityUtils.getSubject());
SecurityUtils.getSubject().getSession().setAttribute("username", username);
return super.onLoginSuccess(token, subject, request, response);
}
/**
* fix relogin
* 注销上次登录, 让上次登录失效.
*
* 注: 这里登出不能使用 Subject 的 logout, 将会导致异常.
* 需使用 session 的 stop 方法来登出.
* @author well
*/
private boolean invalidBeforeLogin(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
String username = getUsername(request);
Subject existSubject = onceLoginSessionMgr.getShiroSubject(username);
if (existSubject != null) {
/*onceLoginSessionMgr.removeShiroSubject(username);
existSubject.getSession().removeAttribute("username");
existSubject.logout(); // fix: Tomcat7 Null Point Exception. logout use session stop
existSubject.getSession().stop();*/
}
SecurityUtils.getSubject().getSession().setAttribute("username", username);
onceLoginSessionMgr.addShiroSubject(username, SecurityUtils.getSubject());
return super.onLoginSuccess(token, subject, request, response);
}
/**
* 重写父类方法,主要是为了写登录失败日志,这里是密码失败日志
*
* @author zhaobing
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
ServletRequest request, ServletResponse response) {
String username = getUsername(request);
SecurityUtils.getSubject().getSession().setAttribute("username", username);
onceLoginSessionMgr.addShiroSubject(username, SecurityUtils.getSubject());
return super.onLoginFailure(token,e,request,response);
}
@Override
protected CaptchaUsernamePasswordToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
String captcha = getCaptcha(request);
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return new CaptchaUsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha,LOGIN_TYPE);
}
public String getCaptchaParam() {
return captchaParam;
}
public void setCaptchaParam(String captchaParam) {
this.captchaParam = captchaParam;
}
protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
}
public int getMaxSessions() {
return maxSessions;
}
public void setMaxSessions(int maxSessions) {
this.maxSessions = maxSessions;
}
public boolean isErrorIfMaximumExceeded() {
return errorIfMaximumExceeded;
}
public void setErrorIfMaximumExceeded(boolean errorIfMaximumExceeded) {
this.errorIfMaximumExceeded = errorIfMaximumExceeded;
}
/**
* 获取当前网络ip
* @param req
* @return
*/
private String getIpAddr(ServletRequest req){
HttpServletRequest request = (HttpServletRequest)req;
String ipAddress = request.getHeader("x-forwarded-for");
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根据网卡取本机配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
}
public class CaptchaFormAuthenticationFilter extends FormAuthenticationFilter {
// 日志组件
private Logger LOG = LoggerFactory.getLogger(CaptchaFormAuthenticationFilter.class);
public static final String DEFAULT_CAPTCHA_PARAM = "captcha";
private String captchaParam = DEFAULT_CAPTCHA_PARAM;
public static final String LOGIN_TYPE = LoginType.SMART.toString();
private int maxSessions = 1;
private boolean errorIfMaximumExceeded = false;
private static final OnceLoginSessionMgr onceLoginSessionMgr = OnceLoginSessionMgr.getInstance();
/**
* fix relogin
* 拒绝二次登录或.
*/
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
if(errorIfMaximumExceeded) {
return rejectSecondLogin(token, subject, request, response);
} else {
return invalidBeforeLogin(token, subject, request, response);
}
}
/**
* fix relogin
* 拒绝二次登录.
*/
private boolean rejectSecondLogin(AuthenticationToken token, Subject subject, ServletRequest request,ServletResponse response) throws Exception {
String username = getUsername(request);
if (onceLoginSessionMgr.getShiroSubject(username) != null) {
/*SecurityUtils.getSubject().logout();
throw new ReLoginException("账号已经登录,请勿重复登录");*/
}
onceLoginSessionMgr.addShiroSubject(username, SecurityUtils.getSubject());
SecurityUtils.getSubject().getSession().setAttribute("username", username);
return super.onLoginSuccess(token, subject, request, response);
}
/**
* fix relogin
* 注销上次登录, 让上次登录失效.
*
* 注: 这里登出不能使用 Subject 的 logout, 将会导致异常.
* 需使用 session 的 stop 方法来登出.
* @author
*/
private boolean invalidBeforeLogin(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
String username = getUsername(request);
Subject existSubject = onceLoginSessionMgr.getShiroSubject(username);
if (existSubject != null) {
/*onceLoginSessionMgr.removeShiroSubject(username);
existSubject.getSession().removeAttribute("username");
existSubject.logout(); // fix: Tomcat7 Null Point Exception. logout use session stop
existSubject.getSession().stop();*/
}
SecurityUtils.getSubject().getSession().setAttribute("username", username);
onceLoginSessionMgr.addShiroSubject(username, SecurityUtils.getSubject());
return super.onLoginSuccess(token, subject, request, response);
}
/**
* 重写父类方法,主要是为了写登录失败日志,这里是密码失败日志
*
* @author
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
ServletRequest request, ServletResponse response) {
String username = getUsername(request);
SecurityUtils.getSubject().getSession().setAttribute("username", username);
onceLoginSessionMgr.addShiroSubject(username, SecurityUtils.getSubject());
return super.onLoginFailure(token,e,request,response);
}
@Override
protected CaptchaUsernamePasswordToken createToken(ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
String captcha = getCaptcha(request);
boolean rememberMe = isRememberMe(request);
String host = getHost(request);
return new CaptchaUsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha,LOGIN_TYPE);
}
public String getCaptchaParam() {
return captchaParam;
}
public void setCaptchaParam(String captchaParam) {
this.captchaParam = captchaParam;
}
protected String getCaptcha(ServletRequest request) {
return WebUtils.getCleanParam(request, getCaptchaParam());
}
public int getMaxSessions() {
return maxSessions;
}
public void setMaxSessions(int maxSessions) {
this.maxSessions = maxSessions;
}
public boolean isErrorIfMaximumExceeded() {
return errorIfMaximumExceeded;
}
public void setErrorIfMaximumExceeded(boolean errorIfMaximumExceeded) {
this.errorIfMaximumExceeded = errorIfMaximumExceeded;
}
/**
* 获取当前网络ip
* @param req
* @return
*/
private String getIpAddr(ServletRequest req){
HttpServletRequest request = (HttpServletRequest)req;
String ipAddress = request.getHeader("x-forwarded-for");
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根据网卡取本机配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
//对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
}
public enum LoginType {
CUSTOM("Custom"), SMART("Smart");
private String type;
private LoginType(String type) {
this.type = type;
}
@Override
public String toString() {
return this.type.toString();
}
}
public class UsernamePasswordToken extends UsernamePasswordToken {
private static final long serialVersionUID = -4007351673293500161L;
private String captcha;
private String ukeyId;
private String loginType;
public UsernamePasswordToken(String username, char[] password,
boolean rememberMe, String host, String captcha, String ukeyId,String loginType) {
super(username, password, rememberMe, host);
this.captcha = captcha;
this.ukeyId = ukeyId;
this.loginType = 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 captcha,String loginType) {
this(username, password, rememberMe, host, captcha, null,loginType);
}
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public String getUkeyId() {
return ukeyId;
}
public void setUkeyId(String ukeyId) {
this.ukeyId = ukeyId;
}
}
public class CustomizedModularRealmAuthenticator extends ModularRealmAuthenticator{
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
// 判断getRealms()是否返回为空
assertRealmsConfigured();
// 强制转换回自定义的CustomizedToken
CaptchaUsernamePasswordToken customizedToken = (CaptchaUsernamePasswordToken) authenticationToken;
// 登录类型
String loginType = customizedToken.getLoginType();
// 所有Realm
Collection realms = getRealms();
// 登录类型对应的所有Realm
Collection typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(loginType))
typeRealms.add(realm);
}
// 判断是单Realm还是多Realm
if (typeRealms.size() == 1)
return doSingleRealmAuthentication(typeRealms.iterator().next(), customizedToken);
else
return doMultiRealmAuthentication(typeRealms, customizedToken);
}
}
@Controller
@RequestMapping(value = "/customLogin")
public class CustomLoginController {
@RequestMapping(method = RequestMethod.GET)
public String customLogin() {
// 如果已登录,直接跳转至主页
ShiroUser user = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
if (user != null) {
return "redirect:custom/main/index";
}
return "account/customLogin";
}
@RequestMapping(method = RequestMethod.POST)
public String fail(@RequestParam(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM) String userName, Model model) {
model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, userName);
return "account/customLogin";
}
}
@Controller
@RequestMapping(value = "/smartLogin")
public class SmartLoginController {
@RequestMapping(method = RequestMethod.GET)
public String smartLogin() {
// 如果已登录,直接跳转至主页
ShiroUser user = (ShiroUser) SecurityUtils.getSubject().getPrincipal();
if (user != null) {
return "redirect:smart/main/index";
}
return "account/login";
}
@RequestMapping(method = RequestMethod.POST)
public String fail(@RequestParam(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM) String userName, Model model) {
model.addAttribute(FormAuthenticationFilter.DEFAULT_USERNAME_PARAM, userName);
return "account/login";
}
}