项目源码:https://github.com/weimingge14/Shiro-project
演示地址:http://liweiblog.duapp.com/Shiro-project/login
SSM + Shiro 整合 (4)- 在 Web 项目中添加 Shiro
本节的目标是整合 Shiro。
步骤1:添加依赖
<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>
步骤2:添加配置文件 shiro.ini
(说明:下面的这个配置是全部写好的配置,请大家要明白每一行配置的意思,一点一点添加完成功能上的测试)
[main]
# 声明一个密码匹配器
credentialsMatcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
# 设置该密码匹配器使用的算法是 md5
credentialsMatcher.hashAlgorithmName=md5
# 声明一个自定义的 Realm
myRealm = com.liwei.shiro.realm.MyRealm
# 将上面声明的密码匹配器注入到自定义 Realm 的属性中去
myRealm.credentialsMatcher=$credentialsMatcher
# 自定义一个权限匹配器
permissionResolver=com.liwei.shiro.permission.UrlPermissionResolver
# 将自定义的权限匹配器注入到自定义 Realm 中
myRealm.permissionResolver = $permissionResolver
# 设置安全管理器的安全数据源为自定义的 Realm
securityManager.realms=$myRealm
# 如果认证不通过,浏览器通过 Get 方式请求到 /login 上
authc.loginUrl=/login
[filters]
# 声明一个自定义的过滤器
resourceCheckFilter = com.liwei.shiro.filter.ResourceCheckFilter
# 为上面声明的自定义过滤器注入属性值
resourceCheckFilter.errorUrl=/unAuthorization
[urls]
# 配置 url 与使用的过滤器之间的关系
/admin/**=authc,resourceCheckFilter
/login=anon
总结:在没有使用 Spring 整合 Shiro 的时候,我们在 web.xml 里面配置监听器和过滤器。整合 Spring 的时候,我们应该在 web.xml 里面配置一个代理。
单纯的 web 应用使用的是 shiro.int 来实例化 Shiro 的各个组件,如果我们使用 Spring 的话,就要使用 Spring 的 Bean 文件了,这样 shiro.ini 文件就要转换成 Spring 的 Bean 文件。
配置与 Spring 的整合,就要添加 Spring 的依赖。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
步骤3:在 web.xml 中添加
<!-- 添加 Shiro 相关配置 -->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
步骤4:编写自定义 Realm
public class MyRealm extends AuthorizingRealm {
@Autowired
private IUserService userService;
private static final Logger logger = LoggerFactory.getLogger(MyRealm.class);
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
logger.info("--- MyRealm doGetAuthorizationInfo ---");
// 获得经过认证的主体信息
User user = (User)principalCollection.getPrimaryPrincipal();
Integer userId = user.getId();
// UserService userService = (UserService)InitServlet.getBean("userService");
List<Resource> resourceList = userService.listAllResource(userId);
List<String> roleSnList = userService.listRoleSnByUser(userId);
List<String> resStrList = new ArrayList<>();
for(Resource resource:resourceList){
resStrList.add(resource.getUrl());
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(new HashSet<>(roleSnList));
info.setStringPermissions(new HashSet<>(resStrList));
// 以上完成了动态地对用户授权
logger.debug("role => " + roleSnList);
logger.debug("permission => " + resStrList);
return info;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
logger.info("--- MyRealm doGetAuthenticationInfo ---");
String username = authenticationToken.getPrincipal().toString();
// String password = new String((char[])authenticationToken.getCredentials());
// 以后我们使用 Spring 管理 Shiro 的时候,就不必要这样得到 UserService 了
// userService = (IUserService) InitServlet.getBean("userService");
// User user = userService.login(username,password);
// 这里应该使用 load 方法,比对用户名的密码的环节应该交给 Shiro 这个框架去完成
User user = userService.loadByUsername(username);
// 第 1 个参数可以传一个实体对象,然后在认证的环节可以取出
// 第 2 个参数应该传递在数据库中“正确”的数据,然后和 token 中的数据进行匹配
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),getName());
// 设置盐值
info.setCredentialsSalt(ByteSource.Util.bytes(username.getBytes()));
return info;
}
}
步骤5:编写登录的方法
@RequestMapping(value = "/")
@Controller
public class LoginController {
private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
@RequestMapping(value = "/login",method = RequestMethod.GET)
public String login(){
return "login";
}
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(User user, Model model){
String username = user.getUsername();
String password = user.getPassword();
logger.debug("username => " + username);
logger.debug("password => " + password);
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
Subject subject = SecurityUtils.getSubject();
String msg = null;
try {
subject.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
msg = e.getMessage();
} catch (IncorrectCredentialsException e){
e.printStackTrace();
msg = e.getMessage();
}
if(msg == null){
return "redirect:/admin/user/list";
}
model.addAttribute("msg",msg);
return "login";
}
@RequestMapping(value = "/logout",method = RequestMethod.GET)
public String logout(Model model){
Subject subject = SecurityUtils.getSubject();
subject.logout();
model.addAttribute("msg","您已经退出登录");
return "login";
}
@RequestMapping(value = "/unAuthorization")
public String unAuthorization(){
return "unAuthorization";
}
}