shiro
登录拦截ShiroConfig,认证Authentication,授权Authorizing
Shiro安全框架的三大核心:
1.Subject
应用代码的直接交互对象就是Subject,也就是说Shiro对外的核心API就是Subject,Subject代表了当前“用户”,这个用户不是指具体的某一个人,可以说与当前应用交互的任何东西都是Subject,与Subject的所有交互都会委托给SecurityManager来执行,可以理解为Subject只是一个充当门面的,真正的幕后老大是SecurityManager,SecurityManager才是实际的执行者。
2.SecurityManager(安全管理器)
所有与安全有关的操作都会与SecurityManager进行交互,并且SecurityManager管理者所有的Subject,可以看出它才是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMvc中的dispatcherServlet(前端控制器)的角色。
3.Realm
Shiro从Realm获取安全数据(用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法,也需要从Realm获取用户的角色\权限来判断用户是否能进行一系列操作。可以把Realm看作DataSource数据源.
1.导入依赖
//核心依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
//用于页面判断的
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2.配置类UserRealm
//继承AuthorizingRealm
public class UserRealm extends AuthorizingRealm {
@Autowired
IUserService userService;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权");
//SimpleAuthorizationInfo创建授权信息对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//给当前用户授权user:add;在这里添加 相当于给所有用户都赋予user:add权限
info.addStringPermission("user:add");
//以下是用数据库给用户赋予权限
//拿到当前登陆的这个对象,这个对象不是指用户对象,可以说与当前应用交互的任何东西都是Subject;
Subject subject = SecurityUtils.getSubject();
//拿到当前登陆的user对象,是认证里面最后一行验证信息里面的user
User currentUser = (User) subject.getPrincipal();//subject.getPrincipal();获取认证里面的user
//设置当前用户的权限,数据库读出 前缀必须要 加user: 不然数据库读不到
info.addStringPermission("user:"+currentUser.getPerms());
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证");
// //用户名 密码 数据库中取
// 获取前端的用户和密码
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
User user = userService.querUserByName(userToken.getUsername());//userToken.getUsername()获取前端用户输入的name值 与数据库的name1相比
if (user == null) {
return null; //UnknownAccountException
}
//获取当前登陆对象
Subject subject = SecurityUtils.getSubject();
//获取Session
Session session = subject.getSession();
session.setAttribute("loginUser", user);
Session session1 = subject.getSession();
session1.setAttribute("loginname", user.getName());
// String password11= user.getPassword();
//密码可以加密:md5,md5盐值加密
//密码认证 shiro做
// 加盐加密四个参数;用加密算法,加密密码,盐,加密次数
String pwd = new SimpleHash("md5", user.getPassword(),
ByteSource.Util.bytes(user.getName()), 5).toString();//加盐加密
System.out.println("加盐加密5次后" + pwd);
//返回验证信息1.用户对象 2.数据库的正确密码 3.盐(这里更具用户名加盐) 4.Reiam名字
//传的是从数据库中获取的password,然后再与token中的password进行对比,匹配上了就通过,匹配不上就报异常。
return new SimpleAuthenticationInfo(user, user.getPassword(),ByteSource.Util.bytes(user.getName()), getName());
}
}
3.ShiroConfig
@Configuration
public class ShiroConfig {
// ShiroFilterFactoryBean
//最后一步
@Bean
public ShiroFilterFactoryBean ShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean Bean = new ShiroFilterFactoryBean();
//设置安全管理器 // 关联DefaultWebSecurityManager
Bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/* anon:无需认证就可以访问
* authc:必须认证了才能访问
* user :必须拥有 记住我 功能才能用
* perms: 拥有对某个资源的权限才能访问
* role:拥有某个角色权限才能访问
* */
Map<String, String> filterMap = new LinkedHashMap<>();
//LinkedHashMap配置拦截的规则,不拦截的放在前面,拦截的放在后面
// System.out.println("shiro41");
//授权,正常情况下,没有授权会跳到没授权页面
filterMap.put("/user/add", "perms[user:add]");//必须要拥有user:add权限才能访问页面
filterMap.put("/user/update", "perms[user:update]");
filterMap.put("/**", "authc");//拦截
//配置记住我或认证通过可以访问的地址
filterMap.put("/**", "user");
Bean.setFilterChainDefinitionMap(filterMap);
//设置拦截后跳转的登陆请求
Bean.setLoginUrl("/tologin");
//设置为未授权的请求跳转的页面
Bean.setUnauthorizedUrl("/noauth");
return Bean;
}
//DefaultWebSecurityManager 核心负责业务操作
@Bean(name = "manager") //第二步 //@Qualifier("userRealm")是将下面的userRealm关联起来的注释
public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
//关联userRealm
manager.setRememberMeManager(rememberMeManager());
manager.setRealm(userRealm);
return manager;
}
//创建realm用户对象 x需要自定义类 第一步
@Bean(name = "userRealm") public UserRealm userRealm() {
return new UserRealm();
}
/**
* cookie管理对象;
* rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager(){
//System.out.println("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
return cookieRememberMeManager;
}
/**
* cookie对象;
* rememberMeCookie()方法是设置Cookie的生成模版,比如cookie的name,cookie的有效时间等等。
* @return
*/
@Bean
public SimpleCookie rememberMeCookie(){
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
//整合ShiroDialect,用来整合shiro thymelaf,这个必须要加
@Bean
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
}
4.Controller
@Controller
public class Mycontroller {
@RequestMapping({"/","/index"})
public String helloshiro(Model model) {
model.addAttribute("msg", "Hello,shiro");
return "index";
}
@RequestMapping("/user/add")
public String add() {
return "user/add";
}
@RequestMapping("/user/update")
public String update() {
return "user/update";
}
@RequestMapping("/tologin")
public String tologin() {
return "login";
}
@RequestMapping("/login") //用户认证
public String login(String name, String password, Model model,boolean rememberMe) {
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登陆数据
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(name, password,rememberMe);
try {
subject.login(usernamePasswordToken); //执行登陆方法,如果没有异常说明ok了,登陆成功
return "forward:/";
} catch (UnknownAccountException e) //用户不存在
{
model.addAttribute("msg", "用户名错误");
return "login";
} catch (IncorrectCredentialsException e) //密码不存在
{
model.addAttribute("msg", "密码错误");
return "login";
}
}
//注销页面
@RequestMapping("/tologout")
public String tologout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/";
}
//未授权页面
@RequestMapping("/noauth")
@ResponseBody
public String unauthiruzed() {
return "未授权无法访问此页面";
}
}
5.实体类必须实现序列化
/*第一步:在Androidstudio中点击File–Setting–Editor–Inspections–Java–Serialization issues–勾选Serializable class without"serialVersionUID"即可,然后点击OK
第二步:选中实体类类名按住Alt+Enter,选择条目,即可生成serialVersionUID
*/
//必须要实现序列化,否则记住我功能失效
public class User implements Serializable {
private static final long serialVersionUID = -2085481127044076202L;
private long id;
private String name;
private String password;
private String perms;
5.前端核心页面
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<h1>首页</h1>
<!--<span style="color: red">${msg}</span>-->
<p th:text="${msg+','+session.loginname}"></p>
<hr/>
<!--从session中判断值-->
<div th:if="${session.loginUser==null}">
<a th:href="@{/tologin}">登陆</a>
</div>
<!--显示拥有add权限的用户-->
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">update</a>
</div>
<div th:if="${session.loginUser!=null}">
<a th:href="@{/tologout}">注销</a>
</div>