- 一步一步教你用shiro——1引入shiro框架
- 一步一步教你用shiro——2配置并自定義realm
- 一步一步教你用shiro——3配置并自定義sessionManager
- 一步一步教你用shiro——4配置并自定義sessionDao
- 一步一步教你用shiro——5配置rememberMe
- 一步一步教你用shiro——6總結(jié)和心得
shiro中realm的是進(jìn)行認(rèn)證和授權(quán)的組件堵腹,自帶了幾種實現(xiàn)答憔,比如jdbcRealm和iniRealm,實際項目中肯定都是自己實現(xiàn)realm
- 首先需要建立用戶表霉晕,存放用戶名忌堂、密碼雁比、權(quán)限信息
CREATE TABLE IF NOT EXISTS `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
`user_name` varchar(10) NOT NULL DEFAULT '' COMMENT '用戶名',
`password` varchar(50) NOT NULL DEFAULT '' COMMENT '用戶密碼盈魁,用戶名為鹽愿汰,五次md5',
`roles` varchar(20) NOT NULL DEFAULT '' COMMENT '角色名,逗號分隔',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_idx_role_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用戶表';
- 實現(xiàn)dao增刪改查(使用的mybatis代理dao)枕屉,封一個service實際操作user表
@Service
public class UserService {
//mybatis代理實現(xiàn)的dao
@Resource
private UserDao userDao;
//根據(jù)用戶名獲得user對象
public User queryUserByName(String name) {
try {
if (StringUtils.isBlank(name)) {
return null;
}
return userDao.queryUserByName(name);
} catch (Exception e) {
log.error("db error when query user:{}", name, e);
}
return null;
}
//根據(jù)用戶名獲得用戶的所有角色
public Set<String> queryUserRole(String userName) {
User user = queryUserByName(userName);
if (user == null) {
return Collections.emptySet();
}
List<String> roleList = StringAssist.splitComma(user.getRoles());
return Sets.newHashSet(roleList);
}
}
- 自定義MyRealm繼承AuthorizingRealm常柄,分別實現(xiàn)認(rèn)證和授權(quán)的方法
- doGetAuthenticationInfo是認(rèn)證的方法,當(dāng)用戶登陸的時候會調(diào)用搀庶,例如下面
@PostMapping("login")
public String login(String username, String password) {
try {
//shiro通過SecurityUtils.getSubject()獲得主體拐纱,主體可以理解為客戶端實例,原理在后面講
Subject subject = SecurityUtils.getSubject();
//已經(jīng)認(rèn)證過哥倔,也就是該客戶端已經(jīng)登陸過
if (subject.isAuthenticated()) {
return "redirect:/static/html/indexLogin.html";
}
//一般都使用UsernamePasswordToken,shiro的token中有Principal和Credentials的概念
//Principal代表當(dāng)前客戶端要登錄的用戶揍庄,Credentials代表證明該用戶身份的憑證
//UsernamePasswordToken將username作為Principal咆蒿,password作為Credentials
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//rememberMe功能后面講
token.setRememberMe(true);
subject.login(token);
} catch (AuthenticationException e) {
//登錄失敗則跳轉(zhuǎn)到登錄失敗頁面,可能是用戶名或密碼錯誤
return "redirect:/static/html/loginError.html";
}
return "redirect:/static/html/indexLogin.html";
}
- doGetAuthorizationInfo是授權(quán)的方法,在攔截器中進(jìn)行權(quán)限校驗的時候會調(diào)用
public class MyRealm extends AuthorizingRealm {
@Resource
private UserService userService;
//用戶的權(quán)限信息包含roles角色和permission權(quán)限兩部分沃测,我這里只使用了角色進(jìn)行進(jìn)行權(quán)限控制
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//principals.getPrimaryPrincipal()獲得的就是當(dāng)前用戶名
if (principals == null || StringUtils.isBlank((String) principals.getPrimaryPrincipal())) {
return null;
}
//將用戶角色信息傳入SimpleAuthorizationInfo
return new SimpleAuthorizationInfo(userService.queryUserRole((String) principals.getPrimaryPrincipal()));
}
//token實際就是在login時傳入的UsernamePasswordToken
//getPrincipal()中只執(zhí)行了getUsername(),getCredentials()只執(zhí)行了getPassword()
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
if (token == null||StringUtils.isBlank((String) token.getPrincipal())) {
return null;
}
//根據(jù)token中的用戶名查庫缭黔,獲得user對象
User user = userService.queryUserByName((String) token.getPrincipal());
if (user == null) {
return null;
}
//SimpleAuthenticationInfo代表該用戶的認(rèn)證信息,其實就是數(shù)據(jù)庫中的用戶名蒂破、密碼馏谨、加密密碼使用的鹽
//存在數(shù)據(jù)庫中的密碼是對用戶真是密碼通過md5加鹽加密得到的,保證安全附迷,及時數(shù)據(jù)泄露惧互,也得不到真正的用戶密碼
//getName()返回該realm的名字,代表該認(rèn)證信息的來源是該realm喇伯,作用不大喊儡,一般都是單realm
//該方法返回后,上層會對token和SimpleAuthenticationInfo進(jìn)行比較稻据,首先比較Principal()艾猜,然后將token的Credentials
//進(jìn)行md5加上SimpleAuthenticationInfo中的鹽加密,加密結(jié)果和SimpleAuthenticationInfo的Credentials比較
return new SimpleAuthenticationInfo(
user.getUserName(), user.getPassword(), ByteSource.Util.bytes(user.getUserName()), getName());
}
- 在securityManager中注入realm捻悯,其中authenticator必須先于realms注入匆赃,這一點非常關(guān)鍵,我之前無論如何都無法授權(quán)今缚,debug發(fā)現(xiàn)authenticator中的realms為空
<!--非web環(huán)境使用DefaultSecurityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--多realm的授權(quán)策略設(shè)置算柳,配置為必須滿足全部realm才算成功,不在realms前配置的話authenticator中的realms集合為空-->
<!--securityManager注入realms的時候荚斯,會把realm也放一份到authenticator中埠居,所以必須寫在realms上面!!!-->
<property name="authenticator">
<bean class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.AllSuccessfulStrategy"/>
</property>
</bean>
</property>
<!--如果只有一個realm的話,可以直接注入realm屬性事期,不需要注入realms屬性-->
<!--為了以后的擴(kuò)展滥壕,即使只有一個realm還是注入了realms屬性(雖然以后估計也都是單realm)-->
<property name="realms">
<list>
<bean class="com.qunar.lfz.shiro.MyRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--指定加密算法-->
<property name="hashAlgorithmName" value="MD5"/>
<!--指定對密碼連續(xù)進(jìn)行5輪md5加密-->
<property name="hashIterations" value="5"/>
</bean>
</property>
</bean>
</list>
</property>
</bean>
- PS:因為我們指定了用戶的原密碼通過5次md5加鹽加密進(jìn)行校驗,這也就要求用戶注冊的時候存入數(shù)據(jù)庫的密碼也是經(jīng)過5次md5加鹽加密的兽泣。shiro提供了Md5Hash工具類绎橘,通過new Md5Hash("原密碼", "鹽值", 5).toString()查看加密后的密碼。