1、盐散列次数以及散列关系
public static void main(String[] args){
// Md5Hash md5Hash = new Md5Hash("abc");
//
// System.out.println("散列后的结果:"+md5Hash);
//为什么要加盐? 给这个密码再添加一层保障
//为了让密码更加安全 更加不容易被破解
Md5Hash md5Hash1 = new Md5Hash("abc", "123");
System.out.println("加盐散列之后的值:"+md5Hash1);
Md5Hash md5Hash2 = new Md5Hash("123abc");
System.out.println("加盐散列之后的值:"+md5Hash2);
//再看下多次散列的问题
Md5Hash md5Hash = new Md5Hash("123", "", 2);
System.out.println("加盐+次数构成的值:"+md5Hash);
Md5Hash md5Hash3 = new Md5Hash("123");
Md5Hash md5Hash4 = new Md5Hash(md5Hash3);
System.out.println("翻译后的值:"+md5Hash4);
}
2、将数据库的密码改成盐+密码散列后的值
Md5Hash md5Hash1 = new Md5Hash("123", "abc");
System.out.println("加盐散列之后的值:"+md5Hash1);
3、重新定义realm
public class MyRealm extends AuthorizingRealm {
private UserDAO userDAO=new UserDAO();
@Override
public String getName() {
return "MyRealm";
}
/**
*这个方法是用来进行认证的
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//第一步:通过token获取到用户数据
String userName = (String) authenticationToken.getPrincipal();
//第二步:通过用户名 去数据库查询用户对象
User user=null;
try {
user = userDAO.findUserByName(userName);
System.out.println("从数据库查询出来的数据是:"+user);
} catch (Exception e) {
System.out.println("查询失败:"+e.fillInStackTrace());
}
if(null==user){ //说明数据库里面没有查询出数据来
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(), //用户名
user.getPassword(), //密码
ByteSource.Util.bytes(user.getSalt()), //这个是从数据库获取的盐
getName()); //realm的名字
return simpleAuthenticationInfo;
}
/**
* 做用户授权的
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
4、编写配置文件
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#散列算法
credentialsMatcher.hashAlgorithmName=md5
#散列次数
credentialsMatcher.hashIterations=1
#将凭证匹配器设置到realm
customRealm=com.test.shiro.md5.MyRealm
customRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$customRealm
5、代码授权
shiro中授权的方式有哪些
1、注解授权
2、过滤器授权
3、页面授权
4、代码授权
用户要授权必须是要在认证的基础上才能授权
问题:在哪里授权?
if(subject.isPermitted("user:select")){
System.out.println("admin....");
}
//表示的是一个一个判断 用户是否具有 某一个权限 (返回的是数组)
boolean[] permitted = subject.isPermitted("", "", "");
//表示的是一个一个判断 用户是否具有 某一个权限 (返回的是数组)
subject.isPermitted(new ArrayList<Permission>());
//玩下角色 (一个一个判断是否具有某一个角色)
subject.hasRoles(new ArrayList<String>());
subject.hasRole(""); //判断是否具有 某一个角色
subject.hasAllRoles(new ArrayList<String>()); //判断是否具有所有权限
6、SpringBoot整合shiro
6.1、导包
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>-->
<!-- <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--导入的是spring对shiro的支持包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
6.2、配置模板引擎的配置(application.properties)
#配置模板引擎
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
6.3、编写login.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="userName"/><span th:text="${userNameError}"></span><br>
密码:<input type="password" name="password"><span th:text="${passwordError}"></span><br>
<input type="submit" value="登陆">
</form>
</body>
</html>
6.4、编写index.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is index Page And you?
</body>
</html>
6.5、编写ShiroConfig的配置文件
@SpringBootConfiguration
public class ShiroConfig {
private Logger logger= LoggerFactory.getLogger(ShiroConfig.class);
//配置咋们的过滤器拦截请求
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
logger.info("shiro的过滤器执行了.....");
//配置如果认证没有通过的话 那么跳转的这个页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
Map<String,String> maps=new LinkedHashMap<>();
//第一个参数是路劲 第二个参数是过滤器的名字
/**
* 常见的过滤器的名字以及含义
* /**:当前以及目录和后面的所有子目录全部都匹配上
* 127.0.0.1:8080/bobo 127.0.0.1:8080/bobo/xiaobobo
* /* :这个相当于只是匹配当前这一级的节点 127.0.0.1:8080/bobo
* 127.0.0.1:8080/bobo/xiaobobo
* authc:认证的过滤器
* anon: 表示的是/toIndex这个请求 不认证就可以访问 (匿名访问)
* maps.put("/toIndex","anon");
* logout:登陆过滤器
* maps.put("/logout","logout")
* perms:权限控制的
* roles:具有某一个角色才能访问
*
* 注意事项: /** 这个配置一定是最后 一个
*
*/
//maps.put("/toIndex","anon"); //表示的是不需要认证就可以访问
maps.put("/login","anon"); //访问请求的地址
maps.put("/**","authc"); //所有的请求都必须在用户认证之后才能访问
shiroFilterFactoryBean.setFilterChainDefinitionMap(maps);
//设置这个安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
//配置的是安全管理器
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("myRealm") MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置校验的realm对象
logger.info("securityManager的过滤器执行了.....");
securityManager.setRealm(myRealm);
return securityManager;
}
//配置的是realm
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
logger.info("myRealm的过滤器执行了.....");
return myRealm;
}
}
6.6、controller的编写
@Controller
public class UserController {
private Logger logger= LoggerFactory.getLogger(UserController.class);
@RequestMapping("/toLogin")
public String toLogin(){
return "login";
}
/**
* 跳转到Index页面的方法
* @return
*/
@RequestMapping("toIndex")
public String toIndex(){
return "index";
}
/**
* 登陆的方法
* @return
*/
@RequestMapping(value = "login")
public String login(User user, Model model){
//封装成请求对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
//获取登陆的主体对象
Subject subject = SecurityUtils.getSubject();
//登陆
try{
subject.login(token);
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
model.addAttribute("userNameError","用户名不对");
return "login";
}catch (IncorrectCredentialsException err){ //说明是密码不对
logger.error("密码不对");
model.addAttribute("passwordError","密码不对");
return "login";
}catch (Exception err){
logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
model.addAttribute("otherError","其他问题造成登陆失败");
return "login";
}
return "index";
}
}
6.7、Realm的编写
public class MyRealm extends AuthorizingRealm {
@Override
public String getName() {
return "MyRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//第一步:获取用户名
String userName= (String) authenticationToken.getPrincipal();
//通过用户名查询用户对象
if(!(userName.equals("admin"))){
return null;
}
//假设查询出来了
User user = new User(1, "admin", "123");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(), getName());
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
6.8、理解图
7、退出的问题
7.1、在页面上编写退出按钮
<a href="/logout">退出</a>
7.2、在全局的过滤器中进行登出过滤器的配置
maps.put("/logout","logout"); //这个就是退出功能
8、密码散列的问题
8.1、在配置文件申明凭证匹配器
//配置密码散列的问题
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//这里就是设置散列的方法的地方
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//设置的是散列的次数
hashedCredentialsMatcher.setHashIterations(1);
return hashedCredentialsMatcher;
}
8.2、改造realm让他支持盐
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//第一步:获取用户名
String userName= (String) authenticationToken.getPrincipal();
//通过用户名查询用户对象
if(!(userName.equals("admin"))){
return null;
}
//假设查询出来了
User user = new User(1, "admin", "e99a18c428cb38d5f260853678922e03");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user.getUserName(),
user.getPassword(),
ByteSource.Util.bytes("abc")
,getName());
return simpleAuthenticationInfo;
}
9、在首页显示用户信息的问题
9.1、通过代码实现在首页显示用户信息
改造 realm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//第一步:获取用户名
String userName= (String) authenticationToken.getPrincipal();
//通过用户名查询用户对象
if(!(userName.equals("admin"))){
return null;
}
//假设查询出来了
User user = new User(1, "admin", "e99a18c428cb38d5f260853678922e03");
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user,
user.getPassword(),
ByteSource.Util.bytes("abc")
,getName());
return simpleAuthenticationInfo;
}
改造Cotnroller
public String login(User user, Model model){
//封装成请求对象
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
//获取登陆的主体对象
Subject subject = SecurityUtils.getSubject();
//登陆
try{
subject.login(token);
}catch (UnknownAccountException err){ //用户名不对
logger.error("用户名不对");
model.addAttribute("userNameError","用户名不对");
return "login";
}catch (IncorrectCredentialsException err){ //说明是密码不对
logger.error("密码不对");
model.addAttribute("passwordError","密码不对");
return "login";
}catch (Exception err){
logger.error("其他问题造成登陆失败:"+err.fillInStackTrace());
model.addAttribute("otherError","其他问题造成登陆失败");
return "login";
}
//首页要显示用户信息
//获取用户信息
//这个方法返回的数据 实际上就是 realm中认证的SimpleAuthticationInfo的第一个参数的数据
User user1= (User) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user",user);
return "index";
}
改在index.html文件
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
欢迎<span th:text="${user.userName}"></span>大爷再次回来.....
this is index Page And you? <br>
<a href="/logout">退出</a>
</body>
</html>
10、授权有很多方式
代码授权
10.1过滤器授权
需求:假设在我的首页有一个按钮 这个按钮访问后台数据的时候 addUser接口这个接口必须要用户具有 user:add权限才能访问
1、在realm中查询用户的权限和角色放到缓存中
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//第一步:先获取用户名
User user= (User) principalCollection.getPrimaryPrincipal();
//第二步:通过用户名查询数据库 当前用户具有的权限 以及角色
// ....
// ...
Set<String> perms=new HashSet<>();
perms.add("ad:add");
Set<String> roles=new HashSet<>();
roles.add("seller");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
simpleAuthorizationInfo.setStringPermissions(perms);
return simpleAuthorizationInfo;
}
2、在HTML页面上设置访问元素
<a href="/addUser">添加用户</a>
3、在配置文件中配置过滤器授权
maps.put("/addUser","perms[user:add]"); //要请求这个地址 用户必
10.2、注解授权
简单的、它要写个注解就可以了
1、在配置文件中配置aop对注解的支持
//下面配置AOP对注解的支持(也就是shiro中注解的支持)
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
//这个是目标过滤器的生命周期
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
2、在方法上使用注解来完成权限的判断
@RequestMapping("toDelete")
@RequiresPermissions({"user:delete"}) //要访问当前的方法必须具有user:delete的权限才能访问
//@RequiresRoles({"seller"})
public String toDelete(){
return "delete";
}
10.3、HTML页面上基于Thymeleaf的支持
1、导包
<!--导入thymeleaf对shiro的支持包-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
2、配置方言
/*配置shiro-dialect这个方言*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
3、使用shiro的标签
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
欢迎<span th:text="${user.userName}"></span>大爷再次回来.....
this is index Page And you? <br>
<a href="/logout">退出</a><br>
<hr>
<a href="/addUser">添加用户(测试过滤器授权)</a>
<br>
<a href="/toDelete">用户删除(测试注解授权)</a>
<!--下面玩下shiro标签库中的标签-->
<!--<shiro:authenticated>
<span>
用户的身份验证是成功的
</span>
</shiro:authenticated>
<shiro:guest>
<span>你是游客</span>
</shiro:guest>
<shiro:hasPermission name="user:add">
<span>用户必须具有某一个权限才能访问</span>
</shiro:hasPermission>
<shiro:hasAllRoles name="buyer,seller">
<span>拥有某一个角色下面才显示</span>
</shiro:hasAllRoles>
<shiro:lacksPermission>
<span>没有某一个权限的时候才能访问</span>
</shiro:lacksPermission>
<shiro:lacksRole name="xxx">
<span>没有某一个角色的时候才能访问</span>
</shiro:lacksRole>
<shiro:notAuthenticated>
<span>没有认证通过才能显示</span>
</shiro:notAuthenticated>
-->
<hr>
<!--下面就是显示用户信息的-->
<shiro:principal property="userName"/> <br>
<!--下面这个标签就表示的是用户已经登陆-->
<shiro:user>
<label>欢迎[<shiro:principal property="userName"/>]登陆</label>
</shiro:user>
</body>
</html>