比如说我想 加一个验证码 认证,原来的userNamePasswordToken 就不够用了,我需要自定义一个新的token.
public class SecurityToken extends UsernamePasswordToken { /** * 验证码 */ private String captcha; /** * 系统生成的验证码(从session获取) */ private String captchaSession; /** * 构造函数 */ public SecurityToken() { super(); } /** * 构造函数 * @param username 用户名 * @param password 用户密码 * @param captcha 验证码 */ public SecurityToken(String username, String password, String captcha) { super(username, password); this.captcha = captcha; } /** * 构造函数 * @param username 用户名 * @param password 用户密码 * @param captcha 验证码 * @param host 主机 */ public SecurityToken(String username, String password, String captcha, String host) { super(username, password, host); this.captcha = captcha; } /** * 构造函数 * @param username 用户名 * @param password 用户密码 * @param captcha 验证码 * @param rememberMe 记住我 */ public SecurityToken(String username, String password, String captcha, boolean rememberMe) { super(username, password, rememberMe); this.captcha = captcha; } /** * 构造函数 * @param username 用户名 * @param password 用户密码 * @param captcha 验证码 * @param rememberMe 记住我 * @param host 主机 */ public SecurityToken(String username, String password, String captcha, boolean rememberMe, String host) { super(username, password, rememberMe, host); this.captcha = captcha; } /** * 获得验证码 * @return 验证码 */ public String getCaptcha() { return captcha; } /** * 设置验证码 * @param captcha 验证码 */ public void setCaptcha(String captcha) { this.captcha = captcha; } /** * 获得系统生成的验证码 * @return 系统生成的验证码 */ public String getCaptchaSession() { return captchaSession; } /** * 设置系统生成的验证码 * @param captchaSession 系统生成的验证码 */ public void setCaptchaSession(String captchaSession) { this.captchaSession = captchaSession; } }
在这基础上,我想要定义认证过滤器:
public class SecurityAuthcFilter extends FormAuthenticationFilter { private static Logger logger = LoggerFactory.getLogger(SecurityAuthcFilter.class); /** * 默认的验证码输入框标识 */ public static final String DEFAULT_CAPTCHA_PARAM = "captcha"; /** * 默认登录后的转向逻辑(是否跳转到登录前的界面) */ public static final boolean DEFAULT_REDIRECT_TO_SAVED_REQUEST = false; /** * 默认的编码方式 */ public static final String DEFAULT_CHARACTER_ENCODING = "UTF-8"; /** * 验证码输入框标识 */ private String captchaParam = DEFAULT_CAPTCHA_PARAM; /** * 登录后的转向逻辑(是否跳转到登录前的界面) */ private boolean redirectToSavedRequest = DEFAULT_REDIRECT_TO_SAVED_REQUEST; /** * 编码方式 */ private String characterEncoding = DEFAULT_CHARACTER_ENCODING; /** * 验证码的属性Key(用于从session中获取系统生成的验证码) */ public static final String CAPTCHA_SESSION = "SECURITY.LOGIN.CAPTCHA"; /** * 登录错误输入框元素名称 */ public static final String LOGIN_FAILED_ELEMENT = "SECURITY.LOGIN.FAILED.ELEMENT"; /** * 登录错误提示信息 */ public static final String LOGIN_FAILED_MESSAGE = "SECURITY.LOGIN.FAILED.MESSAGE"; /** * 根据登录信息创建登录用户的token * @param request ServletRequest对象 * @param response ServletResponse对象 * @return 登录用户的token */ @Override protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { try { request.setCharacterEncoding(characterEncoding); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } String username = getUsername(request); String password = getPassword(request); String captcha = getCaptcha(request); String captchaSession = getCaptchaSession(request); boolean rememberMe = isRememberMe(request); String host = getHost(request); SecurityToken token = new SecurityToken(username, password, captcha, rememberMe, host); token.setCaptchaSession(captchaSession); return token; } /** * 获取验证码输入框标识 * @return 验证码输入框标识 */ public String getCaptchaParam() { return captchaParam; } /** * 设置验证码输入框标识 * @param captchaParam 验证码输入框标识 */ public void setCaptchaParam(String captchaParam) { this.captchaParam = captchaParam; } /** * 获取验证码输入框标识 * @param request ServletRequest对象 * @return 验证码输入框标识 */ protected String getCaptcha(ServletRequest request) { return WebUtils.getCleanParam(request, getCaptchaParam()); } /** * 获取验证码输入框标识 * @param request ServletRequest对象 * @return 验证码输入框标识 */ protected String getCaptchaSession(ServletRequest request) { Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); return (String)session.getAttribute(CAPTCHA_SESSION); } /** * 判断是否跳转到登录前的界面 * @return 是否跳转到登录前的界面 */ public boolean isRedirectToSavedRequest() { return redirectToSavedRequest; } /** * 设置是否跳转到登录前的界面 * @param redirectToSavedRequest 是否跳转到登录前的界面 */ public void setRedirectToSavedRequest(boolean redirectToSavedRequest) { this.redirectToSavedRequest = redirectToSavedRequest; } /** * 获取编码方式 * @return 编码方式 */ public String getCharacterEncoding() { return characterEncoding; } /** * 设置编码方式 * @param characterEncoding 编码方式 */ public void setCharacterEncoding(String characterEncoding) { this.characterEncoding = characterEncoding; } /** * 登录失败后的处理 * @param request ServletRequest对象 * @param ae 用户认证错误异常信息 */ @Override protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) { String element = getUsernameParam(); if(ae instanceof AccountNotFoundException || ae instanceof AccountStatusException || ae instanceof AccountUnknownException) { element = getUsernameParam(); } if(ae instanceof IncorrectPasswordException) { element = getPasswordParam(); } if(ae instanceof IncorrectCaptchaException) { element = getCaptchaParam(); } if(ae instanceof InactivePasswordException) { element = getPasswordParam(); } request.setAttribute(LOGIN_FAILED_ELEMENT, element); request.setAttribute(LOGIN_FAILED_MESSAGE, ae.getMessage()); } /** * 登录成功后的处理 * @param token 登录用户的token * @param subject 登录用户的Shiro信息 * @param request ServletRequest对象 * @param response ServletResponse对象 * @return 是否登录成功 * @throws Exception 异常信息 */ @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { //登录成功后,从session中去除校验码 Session session = subject.getSession(); session.removeAttribute(CAPTCHA_SESSION); //触发执行鉴权方法 SecurityManager.isSessionUserAdminRole(); if (redirectToSavedRequest) { return super.onLoginSuccess(token, subject, request, response); } else { WebUtils.issueRedirect(request, response, getSuccessUrl()); return false; } } }
配置文件配置:
<bean id="securityAuthcFilter" class="com.credithc.scs.security.SecurityAuthcFilter"> <!--<property name="failureKeyAttribute" value=""/>--> </bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/doLogin"/> <property name="successUrl" value="/home"/> <property name="unauthorizedUrl" value="/login"/> <property name="filters"> <util:map> <entry key="authc" value-ref="securityAuthcFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /logout.do*=anon /casticketerror.do*=anon # 权限配置示例 /security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW] /** = authc </value> </property> </bean>
这样再访问 /** 目录的时候就会走到 SecurityAuthcFilter 这个类做认证了。
因为这个类创建的token 是SecurityToken,默认的realm 处理不了,
所以我们还要 定义自己的realm:
public class SecurityRealm extends AuthorizingRealm { private static Logger logger = LoggerFactory.getLogger(SecurityRealm.class); // @Autowired // private ISecurityDelegate securityDelegate; // @Autowired // private ILoginLogDelegate loginLogDelegate; @Autowired private SecurityService securityService; /** * 是否启用验证码 */ private boolean isCaptchaEnable = false; /** * 登录错误的异常信息:验证码错误异常 */ public static final String INCORRECT_CAPTCHA_EXCEPTION_MSG = "验证码错误!"; /** * 登录错误的异常信息:密码错误异常 */ public static final String INCORRECT_PASSWORD_USERNAME_EXCEPTION_MSG = "用户名或密码错误!"; /** * 登录错误的异常信息:密码过期异常 */ public static final String INACTIVE_PASSWORD_EXCEPTION_MSG = "密码已过期,请联系管理员!"; /** * 登录错误的异常信息:未知异常 */ public static final String ACCOUNT_UNKNOWN_EXCEPTION_MSG = "未知系统异常!"; /** * 用户、员工正常状态取值 */ private static final String STATUS_NORMAL = "A"; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SysUserDTO sysUser = (SysUserDTO) principals.fromRealm(getName()).iterator().next(); if (sysUser == null) { logger.info("获取鉴权信息:无法获得登录用户信息"); return null; } try { logger.info("获取鉴权信息:开始获取登录用户【{}】鉴权信息", sysUser.getLoginName()); SimpleAuthorizationInfo info = securityService.doGetAuthorizationInfo(sysUser); logger.info("获取鉴权信息:成功获取登录用户【{}】鉴权信息", sysUser.getLoginName()); return info; } catch (SysException e) { e.printStackTrace(); logger.error("获取鉴权信息:获取登录用户【{}】鉴权信息时发生异常:{}", sysUser.getLoginName(), e.getErrMsg()); throw new AccountUnknownException(e.getErrMsg()); } } /** * 获得用户认证信息 * * @param authenticationToken * 用户登录的token * @return 用户认证信息 * @throws AuthenticationException * 用户认证异常 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { SecurityToken token = (SecurityToken) authenticationToken; Session session = SecurityUtils.getSubject().getSession(); logger.debug("获取认证信息:开始获取登录用户【{}】认证信息", token.getUsername()); /** * 验证码校验 */ if (isCaptchaEnable) { if (!StringUtils.equalsIgnoreCase(token.getCaptcha(), token.getCaptchaSession())) { throw new IncorrectCaptchaException(INCORRECT_CAPTCHA_EXCEPTION_MSG); } logger.debug("获取认证信息:登录用户【{}】通过验证码校验", token.getUsername()); } /** * 登录帐户信息校验(校验帐户是否存在,以及校验帐户状态) */ logger.debug("获取认证信息:开始数据库验证登录用户【{}】信息", token.getUsername()); SysUserDTO sysUser = null; try { sysUser = securityService.doGetAuthenticationInfo(token.getUsername()); } catch (Exception e) { e.printStackTrace(); logger.error("获取认证信息:获取登录用户【{}】鉴权信息时发生异常:{}", token.getUsername(), e.getMessage()); throw new AccountUnknownException(ACCOUNT_UNKNOWN_EXCEPTION_MSG); } if (sysUser == null) { logger.debug("获取认证信息:登录用户【{}】不存在", token.getUsername()); throw new AccountNotFoundException(INCORRECT_PASSWORD_USERNAME_EXCEPTION_MSG); } if (!StringUtils.equals(STATUS_NORMAL, sysUser.getSts()) || !StringUtils.equals(STATUS_NORMAL, sysUser.getSts())) { logger.debug("获取认证信息:登录用户【{}】状态异常", token.getUsername()); throw new AccountStatusException(INCORRECT_PASSWORD_USERNAME_EXCEPTION_MSG); } if (!StringUtils.equals(String.copyValueOf(token.getPassword()), sysUser.getPassword())) { logger.debug("获取认证信息:登录用户【{}】密码错误", token.getUsername()); throw new IncorrectPasswordException(INCORRECT_PASSWORD_USERNAME_EXCEPTION_MSG); } CacheManager cacheManager = CacheManager.getInstance(); String inactiveFlag = (String) cacheManager.get(ConstantsUtils.CACHE_SYSTEM + ".table.cache.idvalue.sysconfig", "INACTIVEL_FLAG"); if (StringUtils.isNotBlank(inactiveFlag) && inactiveFlag.equals("Y")) { if (sysUser.getPwdInactiveTime() != null) { DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { Date startDate = df.parse(sysUser.getPwdInactiveTime()); Date endDate = df.parse(df.format(new Date())); long day = (startDate.getTime() - endDate.getTime()) / (24 * 60 * 60 * 1000); session.setAttribute("inactiveDays", day); if (df.parse(sysUser.getPwdInactiveTime()).getTime() < df.parse(df.format(new Date())).getTime()) { throw new InactivePasswordException(INACTIVE_PASSWORD_EXCEPTION_MSG); } } catch (ParseException e) { e.printStackTrace(); } } } logger.debug("获取认证信息:完成数据库验证登录用户【{}】信息", token.getUsername()); /* * try { LoginLog loginLog = new LoginLog(); * loginLog.setSysUserId(sysUser.getSysUserId()); * loginLog.setIpAddr(token.getHost()); loginLog.setMacAddr(null); * loginLog = loginLogService.insert(loginLog); * session.setAttribute("loginLogId", loginLog.getLoginLogId()); } catch * (SysException e) { e.printStackTrace(); } catch (AppException e) { * e.printStackTrace(); } */ logger.debug("获取认证信息:记录登录用户【{}】日志", token.getUsername()); return new SimpleAuthenticationInfo(sysUser, sysUser.getPassword(), getName()); } /** * 设置是否启用校验码 * * @param captchaEnable * 是否启用校验码 */ public void setCaptchaEnable(boolean captchaEnable) { isCaptchaEnable = captchaEnable; } }
配置realm:
<bean id="securityRealm" class="com.credithc.scs.security.SecurityRealm"> <property name="captchaEnable" value="true"/> <property name="cacheManager" ref="securityCacheManager"/> </bean> <!-- 创建Shiro的securityManager,并设置了realm和cacheManager两个关键属性 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="securityRealm"/> </bean>