ssm+shiro 實現無密碼直接登入
利用shiro可以十分方便,有效的實現介面許可權控制功能。
但是在開發中,會遇到了這樣一種場景:
在微信公眾號開發裡,使用者在公眾號裡輸入使用者名稱和密碼繫結專案,繫結完成後,下次進入專案跳過登入 直接進入專案的首頁。在這個過程中,使用者第二次登入時是使用微信提供的openId直接登入,這是免密登入的。
由於shiro登入是預設需要賬號密碼才能生成token,進而進行登入驗證功能的,所以需要改寫。
注意,此時應該是密碼和免密同時存在的,而不是直接粗暴的去掉密碼驗證。
查找了網上的資料後處理方式有2種
第一種:重寫token,驗證時發現為免密型別時直接返回true
整體思路:
自定義token,加入免密識別符號,在進行授權時判斷是密碼登陸或無密碼登陸,自定義密碼認證方法,即寫一個方法繼承HashedCredentialsMatcher,重寫其中的doCredentialsMatch,將其中的token改寫為自定義的token,最後將自己寫的密碼認證方法注入shiro
1.shiro的配置檔案裡面有一個是<bean id="credentialsMatcher" class="xxxxx.xxxMatcher">這需要一個繼承HashedCredentialsMatcher的子類,重寫doCredentialsMatch方法
2.自定義一個token,繼承自UsernamePasswordToken,新增一個識別符號表名是否免密登入。
3.Subject subject = SecurityUtils.getSubject(); EasyTypeToken token = new EasyTypeToken(loginName); subject.login(token);
4.回到第一步 重寫方法的第一行 直接強轉 token 獲取識別符號 如果為免密登入 直接返回true
--------來自https://blog.csdn.net/Sil_sunday/article/details/81986025?utm_source=copy
他在這篇文章裡有實現程式碼,但是我在實驗的過程中發現 AuthenticationToken不能直接轉換成EasyTypeToken型別,博主可能寫掉了 重寫createToken部分,我自己嘗試重寫,但是始終報錯型別轉換失敗,這種方式我還是沒有實現。但是這個思路是可行的。
第二種:在免密登入時,在後臺寫一個預設的登入密碼
由於第一種方法的問題遲遲沒有解決,就覺得考慮其他方法,後來發現有一種非常簡單的方法:
在免密登入的情況下,寫一個固定的密碼生成token,並將它的MD5加密後的內容放到 SimpleAuthenticationInfo裡,這樣就直接可以登入成功了。
實現程式碼如下:
這是reaml檔案 doGetAuthenticationInfo方法的程式碼
/**
* Authentication(身份驗證):簡稱為“登入/繫結”,即證明使用者是誰。
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1. 把AuthenticationToken轉換為UsernamePasswordToken
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 2. 從UsernamePasswordToken中獲取staffId
String staffId = upToken.getUsername();
//檢視UsernamePasswordToken可知,getCredentials()方法的返回值是char []型別的,所以不能直接轉化成string。
char [] ch = (char[]) token.getCredentials();
//接收輸入的密碼
String password = new String(ch);
// 3. 若使用者不存在,丟擲UnknownAccountException異常
ShiroUser shiroUser = shiroUserService.selectByStaffId(staffId);
if (shiroUser == null) {
throw new UnknownAccountException("使用者不存在!");
}
// 根據使用者的情況,來構建AuthenticationInfo物件並返回,通常使用的實現類為SimpleAuthenticationInfo
//判斷是否是免密登入。
// 在控制器裡,正常的登入(繫結)邏輯是使用者輸入工號和密碼,直接登入是直接把密碼賦值為:無需密碼直接登入
if ("無需密碼直接登入".equals(password)){
//MD5 2次加密後
String passwordMD5 = "fde9e13a5544700bf59ca24b11ffdc81";
String realmName = getName();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(staffId, passwordMD5, null,
realmName);
return info;
}else {
// (2)credentials:密碼
Object credentials = shiroUser.getPassword();
String realmName = getName();
// (4)鹽值:取使用者資訊中唯一的欄位來生成鹽值,避免由於兩個使用者原始密碼相同,加密後的密碼也相同,這裡沒有加鹽
//ByteSource credentialsSalt = ByteSource.Util.bytes(staffId);
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(staffId, credentials, null,
realmName);
return info;
}
}
這是控制器的寫法
@RequestMapping
public void checkBind(HttpServletResponse response, HttpSession session) {
try {
String openId = "xxx";
session.setAttribute("openId", openId);
String staffId = userOpenIdService.selectStaffIdByOpenId(openId);
//如果已繫結就跳到langya的首頁
if (staffId != null) {
//如果已繫結 進行免密登入
Subject subject = SecurityUtils.getSubject();
String password = "無需密碼直接登入";
UsernamePasswordToken token = new UsernamePasswordToken(staffId, password);
token.setRememberMe(false);
subject.login(token);
session.setAttribute("staffId", staffId);
}
//如果沒有繫結就跳到繫結頁
} catch (IOException e) {
e.printStackTrace();
}
}
shiro配置檔案裡:
<!-- 6. 配置ShiroFilter -->
<!-- 6.1 id必須和web.xml中配置的DelegatingFilterProxy的<filter-name>一致。 如果不一致,會丟擲NoSuchBeanDefinitionException異常,因為shiro會在IOC容器中查詢名稱和<filter-name>
值一致的filter bean -->
<bean id="myAuthorizationFilter" class="com.wolwo.shiro.MyAuthorizationFilter"/>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<!-- 配置哪些頁面需要受保護,以及訪問這些頁面需要的許可權 -->
<property name="filters">
<map>
<!--<entry key="authc" value-ref="myLoginFilter"/>-->
<entry key="myAuthorizationFilter" value-ref="myAuthorizationFilter"/>
</map>
</property>
<property name="filterChainDefinitions">
<value>
<!-- 靜態資源 -->
/images/** = anon
/js/** = anon
/css/**= anon
/res/** = anon
/res/** = anon
<!-- 第一次匹配優先的原則 -->
/checkBind = anon
/bind = anon
/login = anon
/user/login = anon
/logout = logout
<!--/** = authc-->
/** = myAuthorizationFilter
</value>
</property>
這樣,就可以很簡單的實現免密登入功能了。