1. shiro介紹
shiro的程序運行流程圖:
Application code:應用程序代碼,開發人員編寫的代碼
Subject:主體,當前用戶
SecurityManager:安全管理器,shiro框架的核心對象,管理各個組件
Realm:類似于Dao,負責訪問安全數據(用戶數據、角色數據、權限數據)
2.shiro的表單認證流程(可以自定義表單認證)
參考http://blog.csdn.net/zcl_love_wx/article/details/51577058
實際中可能會有點差別,靈活處理
1.已經登錄,放行
3.未登錄,判斷是否是登錄請求,不是則跳轉到登錄頁面,并保存當前請求
5.是登錄請求,并且為get請求,則直接跳轉到loginUrl,即登錄頁面
6.是post請求,說明從登錄頁面提交了表單,開始表單,realm驗證
7.驗證失敗,則將錯誤信息存入shiroLoginFailure中,并重新跳轉到登錄頁面,并顯示錯誤信息
8.驗證成功則跳轉到之前保存的請求,沒有則跳轉到successUrl指定頁面
注意:登錄請求/login被攔截進行驗證,最后還是會跳到/login的Controller中
3.實際應用到項目中
- 導入相關的包,shiro-all.jar
- 在web.xml文件中配置Spring用于整合shiro的過濾器
<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>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
- 在Spring-mvc中配置一個bean,id和之前過濾器的名稱相同
<!--shiro配置 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login.html" /> <!--需要登錄的請求在未登錄的情況下會被攔截到這個地址 -->
<!-- <property name="successUrl" value="/system/index" /> -->
<property name="unauthorizedUrl" value="/unauthorized" />
<property name="filters">
<util:map>
<entry key="logout" value-ref="logoutFilter"/>
<entry key="authc" value-ref="formAuthenticationFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/assets/** = anon
/*.ico = anon
/nirvana/** = anon
/login = authc
/logout = logout
/** = authc
</value>
</property>
</bean>
具體細節就不再描述了,請百度自行了解
- 根據shiro的介紹,配置安全管理器securityManager,及其依賴的相關屬性
<!--配置權限核心管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userRealm" />
<property name="cacheManager" ref="cacheManager" />
<property name="sessionManager" ref="sessionManager"></property>
</bean>
<!--替換默認的form 驗證過濾器 -->
<bean id="formAuthenticationFilter"
class="com.sf.tst.web.shiro.filter.CustomFormAuthenticationFilter">
<property name="loginUrl" value="/login"/>
<!--表單上的用戶名/密碼 下次自動登錄的參數名 -->
<property name="usernameParam" value="userid"/>
<property name="passwordParam" value="password"/>
<!--<property name="rememberMeParam" value="rememberMe"/> -->
</bean>
<!-- 退出登錄過濾器 -->
<bean id="logoutFilter" class="com.sf.tst.web.shiro.filter.CoustomLogoutFilter">
<!--重定向 -->
<property name="redirectUrl" value="/login" />
</bean>
<!-- 自定義realm -->
<bean id="userRealm" class="com.sf.tst.web.shiro.realm.UserRealm">
<!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
<!-- <property name="credentialsMatcher" ref="credentialsMatcher" /> -->
</bean>
<!--session管理器,設置過期時間等屬性-->
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="deleteInvalidSessions" value="true"></property>
<property name="sessionValidationInterval" value="1800000"></property>
<property name="globalSessionTimeout" value="1800000"></property>
<property name="cacheManager" ref="cacheManager"></property>
</bean>
<!--默認緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
這里自定義了表單過濾器和登出過濾器
- 自定義表單過濾器CustomFormAuthenticationFilter
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
// 創建 Token
protected CaptchaUsernamePasswordToken createToken(
ServletRequest request, ServletResponse response) {
String username = getUsername(request);
String password = getPassword(request);
String captcha = getCaptcha(request);
boolean rememberMe = isRememberMe(request);
return new CaptchaUsernamePasswordToken(username, password.toCharArray(), rememberMe, captcha);
}
// 認證
protected boolean executeLogin(ServletRequest request,
ServletResponse response) throws Exception {
CaptchaUsernamePasswordToken token = createToken(request, response);
try {
/* doCaptchaValidate((HttpServletRequest) request, token);*/
Subject subject = getSubject(request, response);
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
}
- 自定義登出過濾器
public class CoustomLogoutFilter extends LogoutFilter {
private static final Logger log = LoggerFactory.getLogger(CoustomLogoutFilter.class);
public CoustomLogoutFilter() {}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
String redirectUrl = getRedirectUrl(request, response, subject);
try {
subject.logout();
} catch (SessionException ise) {
log.debug("Encountered session exception during logout. This can generally safely be ignored.", ise);
}
issueRedirect(request, response, redirectUrl);
return false;
}
}
- 自定義BosRealm
public class UserRealm extends AuthorizingRealm{
private static Logger log = LoggerFactory.getLogger(UserRealm.class);
@Autowired
SysUserInfoServiceImpl userService;
/**
* 登錄認證
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
log.info("登錄認證");
// 通過表單接收的用戶名
String username = token.getUsername();
String password = token.getPassword().toString();
if (token.getPassword() != null) {
password = new String(token.getPassword());
}
SysUserInfo user = userService.selectUserByUserName(username);
if (user != null) {
//是否通過 域認證
//boolean flag = ldapAuthentication.authentication(username, password);
boolean flag = username.equals(user.getSysUsername())&&password.equals(user.getSysPwd());/**/
if (flag) {
// 需要驗證用戶是否在本環境中存在
//SysUserInfo user = userService.selectUserByUserName(username);
/* if (user == null) { // 如果沒有此用戶, 則添加一個用戶
user = userService.addNewUser(username);
} else if (user.getIsDeleted()) {
throw new UnknownAccountException("已通過域認證,用戶被鎖定,請聯系管理員。");// 賬號鎖定
}
if (user == null) {
throw new UnknownAccountException("已通過域認證,創建用戶失敗。");
}*/
//如果身份認證驗證成功,返回一個AuthenticationInfo實現;
SimpleAuthenticationInfo info = null;
try {
info = new SimpleAuthenticationInfo(user, password.toCharArray(), user.getSysUsername());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return info;
} else {
log.info(username+"用戶名或密碼錯誤。");
throw new UnknownAccountException("用戶名或密碼錯誤");
}
}else{
log.info(username+"用戶名不存在");
throw new UnknownAccountException("用戶名或密碼錯誤");
}
}
}
- 登錄controller,LoginController
@Controller
public class LoginController {
private static Logger log = LoggerFactory.getLogger(LoginController.class);
/**
* 訪問需要登錄的頁面時被攔截后,需要跳轉到到login頁面
* 結合shiro配置查看
* @throws IOException
*/
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
log.warn("shiro攔截到需要登錄的請求: "+ httpServletRequest.getRequestURI());
Subject subject = SecurityUtils.getSubject();
boolean loginStatus = subject.isAuthenticated();
if (loginStatus) {
log.warn("已登錄狀態,返回到主頁");
Cookie coo = new Cookie("loginMessage", null);
httpServletResponse.addCookie(coo);
return "redirect:login/index";
} else {
log.warn("未登錄狀態,返回登錄頁面");
Cookie cook = new Cookie("loginMessage", null);
httpServletResponse.addCookie(cook);
return "/pages/login";
}
}
@RequestMapping(value = "login/index")
public String toindex(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
log.warn("登錄成功,跳轉到主頁");
return "/pages/index";
}
/**
* 登錄失敗會調用此接口,返回登錄界面
* @throws IOException
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String loginFail(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException{
Subject subject = SecurityUtils.getSubject();
boolean loginStatus = subject.isAuthenticated();
if (loginStatus) {
log.warn("已登錄狀態,返回到主頁");
Cookie coo = new Cookie("loginMessage", null);
httpServletResponse.addCookie(coo);
return "redirect:/index";
} else {
//登錄失敗了 提取錯誤消息
Exception shiroLoginFailureEx = (Exception) httpServletRequest.getAttribute(FormAuthenticationFilter.DEFAULT_ERROR_KEY_ATTRIBUTE_NAME);
String errorMsg = shiroLoginFailureEx.getMessage();
log.warn("登錄失敗后返回login頁面");
Cookie coo = new Cookie("loginMessage", URLEncoder.encode(errorMsg,"UTF-8"));
httpServletResponse.addCookie(coo);
return "/pages/login";
}
}
}
順序:提交表單后-->CustomFormAuthenticationFilter-->UserRealm-->LoginController(/login)