一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第12天,点击查看活动详情。
- subject
应用代码直接交互的对象是Subject,也就是说Shiro的
对外API核心就是Subject,Subject代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等,与Subject的所有交互都会委托给SecurityManager;Subject其实是一个门面,SecurityManager才是实际的执行者。 - SecurityManager
安全管理器,即所有与安全有关的操作都会与SercurityManger交互,并且它管理着所有的Subject,可以看出它是
Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC的DispatcherServlet的角色。 - Realm
Shiro从Realm获取安全数据(如用户,角色,权限),也就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较,来确定用户身份是否合法;也需要从Realm得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以把Realm看成
DataSource;
配置流程
1. 创建config文件
@Configuration
public class ShiroConfig {
/**
* 配置Shiro的过滤器Bean,这个Bean将配置shiro相关的一个规则的拦截
* 例如什么样的请求可以访问什么样的请求不可以访问等等
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//配置用户登录请求,如果需要进行登陆时Shiro就会转到这个请求进入登录页面
shiroFilterFactoryBean.setLoginUrl("/");
//配置登录成功以后转向的请求地址
shiroFilterFactoryBean.setSuccessUrl("/success");
//配置没有权限的请求
shiroFilterFactoryBean.setUnauthorizedUrl("/noPermission");
//配置权限拦截
Map<String,String> filterChainMap = new LinkedHashMap<>();
filterChainMap.put("/login", "anon"); //配置登录请求不需要认证,anon表示某个请求不需要认证
filterChainMap.put("/logout", "logout"); // 配置登出的请求,登出后会清空当前用户的内存
//admin开头的请求需要登录认证, roles[admin]表示所有以admin开头的请求需要有admin的角色才可以使用
filterChainMap.put("/admin/**", "authc,roles[admin]");
filterChainMap.put("/user/**", "authc,roles[user]");
filterChainMap.put("/**", "authc"); // 配置剩余的所有请求全部需要登录认证
//设置权限拦截规则
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainMap);
return shiroFilterFactoryBean;
}
/**
* 配置安全管理器
* @return
*/
@Bean
public SecurityManager securityManager(Realm myRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
/**
* 定义一个Realm,最终使用这个bean返回的对象来完成我们的认证和授权
* @return
*/
@Bean
public MyRealm realm() {
MyRealm myRealm = new MyRealm();
return myRealm;
}
}
2. 创建Realm对象
public class MyRealm extends AuthorizingRealm {
/**
* 用户认证方法
* @param authenticationToken
* @return 用户登录成功后的身份证明
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername(); // 获取页面中传递的用户账号
String password = new String(token.getPassword()); // 获取页面中的用户密码实际工作基本不需要获取
/**
* 认证账号,这里应该从数据库中获取数据
* 如果进入if表示账号不存在要抛出异常
*/
if(!"admin".equals(username) && !"zhangsan".equals(username)) {
throw new UnknownAccountException(); //抛出账号错误异常
}
if("zhangsan".equals(username)) {
throw new LockedAccountException(); //抛出账号错误异常
}
//设置让当前登录用户中的密码数据进行加密
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("MD5");
credentialsMatcher.setHashIterations(2);
this.setCredentialsMatcher(credentialsMatcher);
//对数据库中的密码进行加密
Object obj = new SimpleHash("MD5", "123456", "",2);
/**
* 创建密码认证对象,由Shiro自动认证密码
* 参数1 数据库中的账号(或页面账号均可)
* 参数2 为数据中读取数据来的密码
* 参数3 为当前Realm的名字
* 如果密码认证成功则返回一个用户身份对象,如果密码认证失败Shiro会抛出异常
*/
return new SimpleAuthenticationInfo(username,obj, getName());
}
/**
* 用户授权方法 当用户认证通过每次访问需要授权的请求时都需要执行这段代码来完成授权操作
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户的账户,根据账号从数据库中获取数据
Object obj = principalCollection.getPrimaryPrincipal();
//定义用户角色的Set集合这个集合应该来自数据库
Set<String> roles = new HashSet<>();
//设置角色,这个操作应该是用从数据库中读取数据
if("admin".equals(obj)) {
roles.add("admin");
roles.add("user");
}
if("user".equals(obj)) {
roles.add("user");
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles); //设置角色信息
return info;
}
}
3. Controller内容
@Controller
@SuppressWarnings("all")
public class TestController {
@RequestMapping("/")
public String index() {
return "login";
}
@RequestMapping("/login")
public String login(String username, String password, Model model) {
//获取权限操作对象,利用这个对象来完成操作
Subject subject = SecurityUtils.getSubject();
subject.logout();
//用户是否认证过(是否登录过),进入if表示用户没有认证过需要进行认证
if(!subject.isAuthenticated()) {
//创建用户认证时的身份令牌,并设置我们从页面中传递过来的账号和密码
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
try{
/**
* 指定登录,会自动调用我们Realm对象中的认证方法
* 如果登录失败会抛出各种异常
*/
subject.login(usernamePasswordToken);
}catch (UnknownAccountException e){
e.printStackTrace();
model.addAttribute("errorMessage", "账号错误");
}catch (LockedAccountException e){
e.printStackTrace();
model.addAttribute("errorMessage", "账号被锁定");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
model.addAttribute("errorMessage", "密码错误");
}catch (AuthenticationException e) {
e.printStackTrace();
model.addAttribute("errorMessage", "认证失败!");
return "login";
}
}
return "redirect:/success";
}
@RequestMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
//登出当前账号,清空账号缓存
subject.logout();
return "redirect:/";
}
@RequestMapping("/success")
public String loginSuccess() {
return "success";
}
@RequestMapping("/noPermission")
public String noPermission() {
return "noPermission";
}
@RequestMapping("/admin/test")
@ResponseBody
public String adminTest() {
return "/admin/test请求";
}
@RequestMapping("/user/test")
@ResponseBody
public String userTest() {
return "/user/test请求";
}
}