Shrio初级阶段
一、Springboot与Shiro整合实现用户认证
1.Shiro的核心API
Subject: 用户主体(把操作交给SecurityManager)
SecurityManager:安全管理器(关联Realm)
Realm: Shiro连接数据的桥梁
2.Springboot整合Shiro
2.1 导入Shiro与spring整合依赖
修改pom.xml
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
2.2 自定义Realm类
/**
* 自定义Realm类
*/
public class UserRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权逻辑");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
return null;
}
}
2.3 书写Shiro配置类
三个方法从上到下关联,用@Bean将其配置到Spring中,提高效率
/** * Shiro配置类 */ @Configuration public class ShiroConfig { /** * 1.创建ShiroFilterFactoryBean * 2.创建DefaultWebSecurityManager * 3.创建Realm */ public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(securityManager); return shiroFilterFactoryBean; } @Bean(name="securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); /** * 关联realm */ securityManager.setRealm(userRealm); return securityManager; } @Bean(name="userRealm") public UserRealm getRealm(){ return new UserRealm(); } }
二、使用Shiro内置过滤器实现页面拦截
1.配置过滤器
将拦截的请求放在Map集合中,在这里有两种写法,一种是将每个请求都put进去,另一种则是将所有的请求put进去,在后者中要注意,比如首页不需要拦截,则需要将其优先级先设定为anon,若在后面设定,则会被覆盖失效。
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
System.out.println("hello");
//设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* 添加Shiro内置过滤器
* 常用过滤器:
* anon:无需认证(登录)可以访问
* authc:必须认证才可以访问
* user:如果使用remember me的功能可以直接访问
* perms:该资源必须得到角色权限才可以访问
*/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/test","anon");
//设置拦截的请求
/*filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");*/
filterMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置跳转的控制层方法
shiroFilterFactoryBean.setLoginUrl("/user/login");
return shiroFilterFactoryBean;
}
三、完成Controller与页面
1.Controller层完成检查登录方法
利用UsernamePasswordToken带用户名和密码执行登录方法,在subject.login(token)这行代码,会逐步执行ShiroConfig的方法,最后执行UserRealm中的doGetAuthenticationInfo方法,执行认证逻辑。在这里要注意我对两种异常进行了捕获,UnknownAccountException是对未知账户异常进行捕获,IncorrectCredentialsException是对密码错误异常进行捕获,在这里因为是调用的下层方法,所以在后面的UserRealm类中进行说明。
/**
* 登录逻辑处理
*/
@PostMapping("/checkLogin")
public String checkLogin(String name,String password,Model model){
/**
* 利用Shiro编写认证操作
*/
//获取subject
Subject subject = SecurityUtils.getSubject();
//封装用户数据
UsernamePasswordToken token = new UsernamePasswordToken(name,password);
//执行登录方法
try{
subject.login(token);
return "redirect:/user/test";
}catch (UnknownAccountException e){
//登录失败
model.addAttribute("info","用户名不存在");
return "user/login";
}catch (IncorrectCredentialsException e){
model.addAttribute("info","密码错误");
return "user/login";
}
}
2.重写UserRealm类
重写UserRealm类中doGetAuthenticationInfo方法,因为是初级阶段,暂时不进行数据库的连接,模拟用户名和密码。首先将Controller层方法中封装的UsernamePasswordToken的对象拿到,然后拿到name和password进行比对。
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证逻辑");
//Shiro与数据库信息比对
//假设数据库查出的用户名与密码
String username = "Ysc666";
String password = "123456";
//强转成UsernamePasswordToken
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//进行数据比对
if(!token.getUsername().equals(username)){
//用户名不存在
return null;
}
//判断密码
return new SimpleAuthenticationInfo("",password,"");
}
3.完成页面的书写
提交信息到后台判断,根据结果跳转页面或弹出警告语句
<h3 th:text="${info}"></h3>
<form action="/user/checkLogin" method="post">
name :<input type="text" name="name">
password : <input type="password" name="password">
<input type="submit" value="submit">
</form>
Shiro实用阶段
一、整合Mybatis
1.导入依赖
使用了druid(阿里提供的数据源)
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- lombok依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2.完成相关配置
配置了连接数据库的四个属性以及数据库连接池类型,最后是Mybatis的别名扫描,包扫描。
spring:
thymeleaf:
cache: false
#连接数据库
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springbootdemo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong
username: root
password:
#配置连接池类型 若c3p0就配置c3p0 在这里使用的是阿里的连接池
type: com.alibaba.druid.pool.DruidDataSource
#配置别名扫描
mybatis:
type-aliases-package: com.springbootshiro.entity
mapper-locations: classpath*:/mapper/**Mapper.xml
二、完成基本代码
@Data
public class UserShiro {
private Integer id;
private String username;
private String password;
private String perms;
}
@Repository
public interface UserShiroMapper {
@Select("select id,username,password from user_shiro where username=#{name}")
UserShiro selectByName(@Param("name") String name);
UserShiro selectById(Integer id);
}
@Service("userShiroService")
public class UserShiroServiceImpl implements UserShiroService {
@Autowired
UserShiroMapper userShiroMapper;
@Override
public UserShiro selectByName(String name) {
UserShiro userShiro = userShiroMapper.selectByName(name);
return userShiro;
}
@Override
public UserShiro selectById(Integer id) {
UserShiro userShiro = userShiroMapper.selectById(id);
return userShiro;
}
}
三、完成授权逻辑
1.重写UserRealm
重写UserRealm中的doGetAuthorizationInfo方法,用后台数据库查询到的perms(权限)进行逻辑操作。首先通过SecurityUtils的方法拿到当前subject,再通过getPrincipal方法拿到当前用户信息,然后查到用户的perms.
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
/**
* 连接数据库
*/
System.out.println("执行授权逻辑");
//给资源授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获取到当前用户
Subject subject = SecurityUtils.getSubject();
UserShiro userShiro = (UserShiro)subject.getPrincipal();
UserShiro user = userShiroService.selectById(userShiro.getId());
//添加资源的授权字符串
info.addStringPermission(user.getPerms());
return info;
}
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
2.对拦截逻辑增加授权管理
在ShiroConfig中对需要授权管理增加perms的权限,只有后台查询到的权限满足,才能访问该路径,否则会跳到未授权提示页面
/**
* 授权过滤器
* 注意:当前授权拦截后,Shiro会跳转到未授权页面
*/
filterMap.put("/user/add","perms[add]");
filterMap.put("/user/udpate","perms[update]");
filterMap.put("/user/test","anon");
filterMap.put("/user/checkLogin","anon");
//设置拦截的请求
/*filterMap.put("/user/add","authc");
filterMap.put("/user/update","authc");*/
filterMap.put("/user/*","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//设置跳转的控制层方法
shiroFilterFactoryBean.setLoginUrl("/user/login");
//设置未授权提示页面
shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauth");