开始之前,我们先造一些数据:
INSERT INTO test.sys_permission VALUES ('1', 1, '用户管理', '0', '0/', 'userInfo:view', 'menu', 'userInfo/userList');
INSERT INTO test.sys_permission VALUES ('2', 1, '用户添加', '1', '0/1', 'userInfo:add', 'button', 'userInfo/userAdd');
INSERT INTO test.sys_permission VALUES ('3', 1, '用户删除', '1', '0/1', 'userInfo:del', 'button', 'userInfo/userDel');
INSERT INTO test.sys_role VALUES ('1', 1, '管理员', 'admin');
INSERT INTO test.sys_role VALUES ('2', 1, 'VIP会员', 'vip');
INSERT INTO test.user_info (uid,username,name,password,salt,state) VALUES ('1', 'admin','管理员' , 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
INSERT INTO test.sys_role_permission VALUES ('1', '1');
INSERT INTO test.sys_role_permission VALUES ('1', '2');
INSERT INTO test.sys_user_role VALUES ('1', '1');
数据有了,下面开始Shrio的配置学习。
开始之前先添加Shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.2</version>
</dependency>
1.Realms
Realm是一个Dao,通过它来验证用户身份和权限。这里Shiro不做权限的管理工作,需要我们自己管理用户权限,只需要从我们的数据源中把用户和用户的角色权限信息取出来交给Shiro即可。
在config
包下再建一个包Shiro
,然后在Shiro包下建一个MyShiroRealm
类,继承AuthorizingRealm抽象类。
public class MyShiroRealm extends AuthorizingRealm {
@Resource
private UserInfoService userInfoService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("开始身份验证");
String username = (String) token.getPrincipal(); //获取用户名,默认和login.html中的username对应。
UserInfo userInfo = userInfoService.findByUsername(username);
if (userInfo == null) {
//没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常
return null;
}
//验证通过返回一个封装了用户信息的AuthenticationInfo实例即可。
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户信息
userInfo.getPassword(), //密码
getName() //realm name
);
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userInfo.getCredentialsSalt())); //设置盐
return authenticationInfo;
}
//当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("开始权限配置");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();
for (SysRole role: userInfo.getRoleList()) {
authorizationInfo.addRole(role.getRole());
for (SysPermission p: role.getPermissions()) {
authorizationInfo.addStringPermission(p.getPermission());
}
}
return authorizationInfo;
}
}
重载以上两个方法来配置用户身份验证和权限验证。
别忘了在Service包下新建个UserInfoService和它的实现类:
public interface UserInfoService {
public UserInfo findByUsername(String username);
}
@Service
public class UserInfoServiceImpl implements UserInfoService {
@Resource
private UserInfoRepository userInfoRepository;
@Override
public UserInfo findByUsername(String username) {
System.out.println("UserInfoServiceImpl.findByUsername");
return userInfoRepository.findByUsername(username);
}
}
在dao
层下新建一个UserInfo的repository
public interface UserInfoRepository extends CrudRepository<UserInfo, Long> {
public UserInfo findByUsername(String username);
public UserInfo save(UserInfo userInfo);
}
2.接下来配置Shiro的关键部分
这里要配置的是ShiroConfig类,Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
@Configuration
public class ShiroConfiguration {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/favicon.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
//authc表示需要验证身份才能访问,还有一些比如anon表示不需要验证身份就能访问等。
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
// shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //这里设置403并不会起作用,参考http://www.jianshu.com/p/e03f5b54838c
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm()); //将Realm注入到SecurityManager中。
return securityManager;
}
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); //设置解密规则
return myShiroRealm;
}
//因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
}
关于为什么设置filterChainDefinitionMap.put("/favicon.ico", "anon");
,请参考Shiro登录后下载favicon.ico问题
3.修改我们的HomeController中的/login
请求
// 这里如果不写method参数的话,默认支持所有请求,如果想缩小请求范围,还是要添加method来支持get, post等等某个请求。
@RequestMapping("/login")
public String login(HttpServletRequest request, Map<String, Object> map) throws Exception {
System.out.println("HomeController.login");
// 登录失败从request中获取shiro处理的异常信息。
// shiroLoginFailure:就是shiro异常类的全类名.
Object exception = request.getAttribute("shiroLoginFailure");
String msg = "";
if (exception != null) {
if (UnknownAccountException.class.isInstance(exception)) {
System.out.println("账户不存在");
msg = "账户不存在或密码不正确";
} else if (IncorrectCredentialsException.class.isInstance(exception)) {
System.out.println("密码不正确");
msg = "账户不存在或密码不正确";
} else {
System.out.println("其他异常");
msg = "其他异常";
}
}
map.put("msg", msg);
// 此方法不处理登录成功,由shiro进行处理.
return "login";
}
这里@RequestMapping
之所以没加method是因为如果用户没登录,Shiro会调用get方法请求/login
,而后面我们在login页面会用post请求发送form表单,所以这里就没设置method(默认支持所有请求)。
好,启动项目。这时候我们再访问http://localhost:8080/index
会跳转到登录页面,因为我们设置了filterChainDefinitionMap.put("/**", "authc");
,所以要先验证身份才能访问。同时因为设置了shiroFilterFactoryBean.setLoginUrl("/login");
,所以会跳转到登录页面。这时候在登录页面输入正确的用户名密码就可以登录了,登录成功会自动跳转到index页面。
这一节先到此为止。
到此的项目结构:
SpringBoot + Shiro (一)基础工程搭建
SpringBoot + Shiro (二)身份校验和角色设置
SpringBoot + Shiro (三)权限
SpringBoot + Shiro (四)缓存&记住密码
SpringBoot + Shiro (五)验证码
最后,感谢几位作者的文章解惑:
springboot整合shiro-登录认证和权限管理
Spring Boot Shiro权限管理【从零开始学Spring Boot】
Spring boot 中使用Shiro
最后帮朋友打个小广告