本篇博文是「Java秒殺系統實戰系列文章」的第五篇,在本篇博文中,咱們將整合權限認證-受權框架Shiro,實現用戶的登錄認證功能,主要用於:要求用戶在搶購商品或者秒殺商品時,限制用戶進行登錄!並對於特定的url(好比搶購請求對應的url)進行過濾(即當用戶訪問指定的url時,須要要求用戶進行登錄)。
前端
對於Shiro,相信各位小夥伴應該據說過,甚至應該也使用過!簡單而言,它是一個很好用的用戶身份認證、權限受權框架,能夠實現用戶登陸認證,權限、資源受權、會話管理等功能,在本秒殺系統中,咱們將主要採用該框架實現對用戶身份的認證和用戶的登陸功能。
值得一提的是,本篇博文介紹的「Shiro實現用戶登陸認證」功能模塊涉及到的數據庫表爲用戶信息表user,下面進入代碼實戰環節。git
<!--shiro權限控制-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>複製代碼
(2)緊接着是在UserController控制器中開發用戶前往登陸、用戶登陸以及用戶退出登陸的請求對應的功能方法,其完整的源代碼以下所示:
web
@Autowired
private Environment env;
//跳到登陸頁
@RequestMapping(value = {"/to/login","/unauth"})
public String toLogin(){
return "login";
}
//登陸認證
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(@RequestParam String userName, @RequestParam String password, ModelMap modelMap){
String errorMsg="";
try {
if (!SecurityUtils.getSubject().isAuthenticated()){
String newPsd=new Md5Hash(password,env.getProperty("shiro.encrypt.password.salt")).toString();
UsernamePasswordToken token=new UsernamePasswordToken(userName,newPsd);
SecurityUtils.getSubject().login(token);
}
}catch (UnknownAccountException e){
errorMsg=e.getMessage();
modelMap.addAttribute("userName",userName);
}catch (DisabledAccountException e){
errorMsg=e.getMessage();
modelMap.addAttribute("userName",userName);
}catch (IncorrectCredentialsException e){
errorMsg=e.getMessage();
modelMap.addAttribute("userName",userName);
}catch (Exception e){
errorMsg="用戶登陸異常,請聯繫管理員!";
e.printStackTrace();
}
if (StringUtils.isBlank(errorMsg)){
return "redirect:/index";
}else{
modelMap.addAttribute("errorMsg",errorMsg);
return "login";
}
}
//退出登陸
@RequestMapping(value = "/logout")
public String logout(){
SecurityUtils.getSubject().logout();
return "login";
}複製代碼
其中,在匹配用戶的密碼時,咱們在這裏採用的Md5Hash的方法,即MD5加密的方式進行匹配(由於數據庫的user表中用戶的密碼字段存儲的正是採用MD5加密後的加密串)
redis
前端頁面login.jsp的內容比較簡單,只須要用戶輸入最基本的用戶名和密碼便可,以下圖所示爲該頁面的部分核心源代碼:spring
當前端提交「用戶登陸」請求時,將以「提交表單」的形式將用戶名、密碼提交到後端UserController控制器對應的登陸方法中,該方法首先會進行最基本的參數判斷與校驗,校驗經過以後,會調用Shiro內置的組件SecurityUtils.getSubject().login()方法執行登陸操做,其中的登陸操做將主要在 「自定義的Realm的doGetAuthenticationInfo方法」中執行。
(3)接下來是基於Shiro的AuthorizingRealm,開發自定義的Realm,並實現其中的用戶登陸認證方法,即doGetAuthenticationInfo()方法。其完整的源代碼以下所示:
數據庫
/**
* 用戶自定義的realm-用於shiro的認證、受權
* @Author:debug (SteadyJack)
* @Date: 2019/7/2 17:55
**/
public class CustomRealm extends AuthorizingRealm{
private static final Logger log= LoggerFactory.getLogger(CustomRealm.class);
private static final Long sessionKeyTimeOut=3600_000L;
@Autowired
private UserMapper userMapper;
//受權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//認證-登陸
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken;
String userName=token.getUsername();
String password=String.valueOf(token.getPassword());
log.info("當前登陸的用戶名={} 密碼={} ",userName,password);
User user=userMapper.selectByUserName(userName);
if (user==null){
throw new UnknownAccountException("用戶名不存在!");
}
if (!Objects.equals(1,user.getIsActive().intValue())){
throw new DisabledAccountException("當前用戶已被禁用!");
}
if (!user.getPassword().equals(password)){
throw new IncorrectCredentialsException("用戶名密碼不匹配!");
}
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user.getUserName(),password,getName());
setSession("uid",user.getId());
return info;
}
/**
* 將key與對應的value塞入shiro的session中-最終交給HttpSession進行管理(若是是分佈式session配置,那麼就是交給redis管理)
* @param key
* @param value
*/
private void setSession(String key,Object value){
Session session=SecurityUtils.getSubject().getSession();
if (session!=null){
session.setAttribute(key,value);
session.setTimeout(sessionKeyTimeOut);
}
}
}複製代碼
其中,userMapper.selectByUserName(userName);主要用於根據userName查詢用戶實體信息,其對應的動態Sql的寫法以下所示:
apache
<!--根據用戶名查詢-->
<select id="selectByUserName" resultType="com.debug.kill.model.entity.User">
SELECT <include refid="Base_Column_List"/>
FROM user
WHERE user_name = #{userName}
</select>複製代碼
值得一提的是,當用戶登陸成功時(即用戶名和密碼的取值跟數據庫的user表相匹配),咱們會藉助Shiro的Session會話機制將當前用戶的信息存儲至服務器會話中,並緩存必定時間!(在這裏是3600s,即1個小時)!
(4)最後是咱們須要實現「用戶在訪問待秒殺商品詳情或者搶購商品或者任何須要進行攔截的業務請求時,如何自動檢測用戶是否處於登陸的狀態?若是已經登陸,則直接進入業務請求對應的方法邏輯,不然,須要前往用戶登陸頁要求用戶進行登陸」。
基於這樣的需求,咱們須要藉助Shiro的組件ShiroFilterFactoryBean 實現「用戶是否登陸」的判斷,以及藉助FilterChainDefinitionMap攔截一些須要受權的連接URL,其完整的源代碼以下所示:
後端
/**
* shiro的通用化配置
* @Author:debug (SteadyJack)
* @Date: 2019/7/2 17:54
**/
@Configuration
public class ShiroConfig {
@Bean
public CustomRealm customRealm(){
return new CustomRealm();
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
securityManager.setRealm(customRealm());
securityManager.setRememberMeManager(null);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/to/login");
bean.setUnauthorizedUrl("/unauth");
//對於一些受權的連接URL進行攔截
Map<String, String> filterChainDefinitionMap=new HashMap<>();
filterChainDefinitionMap.put("/to/login","anon");
filterChainDefinitionMap.put("/**","anon");
filterChainDefinitionMap.put("/kill/execute","authc");
filterChainDefinitionMap.put("/item/detail/*","authc");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
}複製代碼
從上述該源代碼中能夠看出,Shiro的ShiroFilterFactoryBean組件將會對 URL=/kill/execute 和 URL=/item/detail/* 的連接進行攔截,即當用戶訪問這些URL時,系統會要求當前的用戶進行登陸(前提是用戶還沒登陸的狀況下!若是已經登陸,則直接略過,進入實際的業務模塊!)
瀏覽器
除此以外,Shiro的ShiroFilterFactoryBean組件還設定了 「前往登陸頁」和「用戶沒受權/沒登陸的前提下的調整頁」的連接,分別是 /to/login 和 /unauth!
(5)至此,整合Shiro框架實現用戶的登陸認證的先後端代碼實戰已經完畢了,將項目/系統運行在外置的tomcat服務器中,打開瀏覽器便可訪問進入「待秒殺商品的列表頁」,點擊「詳情」,此時,因爲用戶還沒登錄,故而將跳轉至用戶登陸頁,以下圖所示:
緩存
輸入用戶名:debug,密碼:123456,點擊「登陸」按鈕,便可登陸成功,併成功進入「詳情頁」,以下圖所示:
登陸成功以後,再回到剛剛上一個列表頁,即「待秒殺商品的列表頁」,再次點擊「詳情」按鈕,此時會直接進入「待秒殺商品的詳情頁」,而不會跳轉至「用戶登陸頁」,並且用戶的登陸態將會持續1個小時!(這是藉助Shiro的Session的來實現的)。
一、目前,這一秒殺系統的總體構建與代碼實戰已經所有完成了,完整的源代碼數據庫地址能夠來這裏下載:gitee.com/steadyjack/… 記得Fork跟Star啊!!!