Springboot整合shiro
1. 基本的整合
1.1 导包
<dependencies>
<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.3</version>
</dependency>
<!--shiro整合spring-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<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.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
1.2 编写全局配置文件
# thymeleaf的配置
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.cache=false
# mybatis 相关配置
mybatis.type-aliases-package=com.wcc.springboot.dao
mybatis.mapper-locations=classpath:/mapping/*.xml
# 数据库相关配置
spring.datasource.username=root
spring.datasource.url=jdbc:mysql:///mydb_01?\
useUnicode=true&useJDBCCompliantTimezoneShift=true&\
useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
# 给应用取个名字
spring.application.name=spingboot-shiro
1.3 编写启动程序
@SpringBootApplication
public class SpringbootShiroApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootShiroApplication.class, args);
}
}
1.4 编写配置文件
@SpringBootConfiguration
@ComponentScan(basePackages = {"com.wcc.springboot"})
@MapperScan(basePackages = {"com.wcc.springboot.mapper"})
public class AppConfig {
}
1.5 编写dao层,一定要实现序列化
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 7905137403214968198L;
private int id;
private String name;
private String password;
private String salt;
}
1.6 编写Mapper接口
public interface UserMapper {
User fingByName( String name);
}
1.7 编写mapper配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wcc.springboot.mapper.UserMapper">
<select id="fingByName" resultType="com.wcc.springboot.dao.User">
SELECT * from user WHERE name=#{name}
</select>
</mapper>
1.8 编写realm
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
public String getName(){
return "MyRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo (
AuthenticationToken authenticationToken ) throws AuthenticationException {
String name = (String) authenticationToken.getPrincipal();
User user = userService.findUserByName(name);
if (user==null){
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new
SimpleAuthenticationInfo(user.getName(),
user.getPassword(), getName());
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo ( PrincipalCollection principalCollection ) {
return null;
}
}
1.9 编写shiro的配置类
@SpringBootConfiguration
public class ShiroConfig {
// 配置过滤拦截请求
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 配置如果没有认证通过的话,跳转到这个页面
factoryBean.setLoginUrl("/toLogin");
/* 常见的过滤器,及其名字
authc:认证的过滤器
/* 和 /* 的区别 :/* 只能匹配当前这一级节点 eg:.../chaochao
/** 可以匹配当前目录以及他的子目录 eg:.../chaochao/chao
anon:表示的是某个请求,可以不认证进行访问(匿名访问)
eg:map.put("/ltoIndex",anon) toIndex可以进行匿名访问
logout:登出过滤器
eg:map.put("/logout",logout)
perms:权限控制的
roles:具有某一个角色才能够访问
*/
// 这个是要有顺序的
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 第一个表示的是路径,第二个参数表示的是过滤器名字
// login这个路径,允许请求
map.put("/login","anon");
// 任何路径只有认证通过才可以访问
map.put("/**","authc");
factoryBean.setFilterChainDefinitionMap(map);
factoryBean.setSecurityManager(securityManager);
return factoryBean;
}
// 配置安全管理器
@Bean
public DefaultWebSecurityManager securityManager(
@Qualifier("myRealm")MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
return securityManager;
}
// 配置realm
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
return myRealm;
}
}
1.10 编写controller层
@Controller
public class UserController {
private Logger log = LoggerFactory.getLogger(UserController.class);
@RequestMapping("toLogin")
public String toLogin(){
return "login";
}
@RequestMapping("toIndex")
public String toIndex(){
return "index";
}
@RequestMapping(value = "login",method = RequestMethod.POST)
public String login(User user){
// 获取token
UsernamePasswordToken token = new
UsernamePasswordToken(user.getName(), user.getPassword());
// 获取主体
Subject subject = SecurityUtils.getSubject();
// 登录
try {
subject.login(token);
} catch (UnknownAccountException e) {
log.info("用户名异常");
return "login";
} catch (IncorrectCredentialsException e){
log.info("密码错误");
return "login";
}
return "success";
}
}
1.11 编写html页面
index页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is index page and you?
</body>
</html>
login页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="name"/>
密码: <input type="password" name="password">
<input type="submit" value="点击提交">
</form>
</body>
</html>
succcess页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
恭喜您,登陆成功
</body>
</html>
2. 退出的问题
2.1 在html页面添加退出链接
success页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
恭喜您,登陆成功
<a href="/logout">退出</a>
</body>
</html>
2.2 在shiro的配置文件中添加登出过滤器
map.put("/login","anon");
// 添加登出过滤器
map.put("/logout","logout");
// 任何路径只有认证通过才可以访问
map.put("/**","authc");
3. 密码散列的问题
3.1 在shiro配置类中添加密码散列的配置
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
// 设置的是散列的方法的地方(使用什么进行加密)
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
// 散列的次数
hashedCredentialsMatcher.setHashIterations(1);
return hashedCredentialsMatcher;
}
3.2 改造reaml让他支持盐
@Override
protected AuthenticationInfo doGetAuthenticationInfo (
AuthenticationToken authenticationToken ) throws AuthenticationException {
String name = (String) authenticationToken.getPrincipal();
User user = userService.findUserByName(name);
if (user==null){
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new
SimpleAuthenticationInfo(user.getName(),
user.getPassword(),
// 让realm支持盐
ByteSource.Util.bytes(user.getSalt()),
getName());
return simpleAuthenticationInfo;
}
3.3 在shiro的配置文件中让realm支持密码凭证器
@Bean
public MyRealm myRealm(){
MyRealm myRealm = new MyRealm();
myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myRealm;
}
4.在首页显示用户信息的问题
4.1 改造controller层
// 这个方法返回的数据其实就是realm中SimpleAuthenticationInfo中的第一个数据
String name = (String) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("name",name);
return "success";
}
}
4.2 改造success.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="${name}"></span>大爷
恭喜您,登陆成功
<a href="/logout">退出</a>
</body>
</html>
5. 授权的方式
5.1 过滤器授权
需求:访问userAdd这个接口的时候,必须有user:add这个权限
5.1.1 在realm文件中查询用户的角色和权限放入到缓存中
@Override
protected AuthorizationInfo doGetAuthorizationInfo (
PrincipalCollection principalCollection ) {
// 获取用户名
String name = (String) principalCollection.getPrimaryPrincipal();
// 在数据库中通过用户名查询角色和权限,封装到集合中(下面进行模拟)
Set<String> perms = new HashSet<>();
perms.add("user:add");
Set<String> roles = new HashSet<>();
roles.add("buyer");
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
simpleAuthorizationInfo.setStringPermissions(perms);
return simpleAuthorizationInfo;
}
}
5.1.2 编写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="${name}"></span>大爷
恭喜您,登陆成功
<a href="/logout">退出</a>
<hr>
<a href="/toAdd">添加(过滤器权限测试)</a>
</body>
</html>
5.1.3 编写shiroConfig文件中的配置过滤器拦截请求
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 配置如果没有认证通过的话,跳转到这个页面
factoryBean.setLoginUrl("/toLogin");
// 如果没有访问权限,跳转到这个页面给以说明
factoryBean.setUnauthorizedUrl("/toshibai");
// 这个是要有顺序的
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 第一个表示的是路径,第二个参数表示的是过滤器名字
// login这个路径,允许请求
map.put("/login","anon");
map.put("/logout","logout");
// 要请求这个地址,必须有user:add权限
map.put("/toAdd","perms[user:ad]");
// 任何路径只有认证通过才可以访问
map.put("/**","authc");
factoryBean.setFilterChainDefinitionMap(map);
factoryBean.setSecurityManager(securityManager);
return factoryBean;
}
5.1.4 编写controller
@RequestMapping("toAdd")
public String toAdd(){
return "add";
}
@RequestMapping("/toshibai")
private String unAuthorization(){
return "shibai";
}
5.2 注解授权
5.2.1 在shiroConfig文件中配置aop对注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor attributeSourceAdvisor(
@Qualifier("securityManager")DefaultWebSecurityManager securityManager ){
AuthorizationAttributeSourceAdvisor attributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
attributeSourceAdvisor.setSecurityManager(securityManager);
return attributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator =
new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
5.2.2 在html页面中添加元素测试注解
<a href="/toadd1">添加1(注解测试)</a>
5.2.3 在controller中测试
@RequestMapping("toadd1")
@RequiresPermissions({"user:add"})
public String toadd1(){
return "add";
}
5.3 使用html标签
5.3.1 导包 thymeleaf对shiro的支持包
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
5.3.2 在shiroConfig中配置shiro方言
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
5.3.3 使用html标签测试
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<body>
<!--<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>
6. 缓存的使用
为什么要使用缓存
每一次 授权的时候 都会去访问咋们的 realm中的授权的方法
这样的话咋们的数据库的压力就会比较大 这种情况下 咋们缓存就应运而生了
缓存是缓存的是咋们的的这个授权信息的(不是其他信息)
6.1 导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
6.2 在resources下编写ehcache.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<diskStore path="F:\mytemp" />
<cache name="users"
timeToLiveSeconds="300"
maxEntriesLocalHeap="1000"/>
<!--
name:缓存名称。
maxElementsInMemory:缓存最大个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
maxElementsOnDisk:硬盘最大缓存个数。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
-->
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
6.3 在shiroConfig中编写EhcacheManager
@Bean
public EhCacheManager ehCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
}
6.4 将EncacheManager放入到安全管理器
@Bean
public DefaultWebSecurityManager securityManager(
@Qualifier("myRealm")MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(ehCacheManager());
return securityManager;
}
7. session的管理
7.1 在shiroConfig中编写DefaultWebSessionManager
// 设置session的管理
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager =
new DefaultWebSessionManager();
// 设置session到期是否自动删除
sessionManager.setDeleteInvalidSessions(true);
// 设置session的超时时间(单位:s)
sessionManager.setGlobalSessionTimeout(15);
return sessionManager;
}
7.2 将session放入安全管理器中
// 配置安全管理器
@Bean
public DefaultWebSecurityManager securityManager(
@Qualifier("myRealm")MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(ehCacheManager());
// 将session放入到安全管理器中
securityManager.setSessionManager(sessionManager());
return securityManager;
}
8. RememberMe功能的实现
8.1 在shiroConfig文件中配置CookieSessionManager
@Bean
public CookieRememberMeManager rememberMeManager(){
CookieRememberMeManager rememberMeManager =
new CookieRememberMeManager();
rememberMeManager.setCookie(simpleCookie());
return rememberMeManager;
}
@Bean
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
// 设置cookie的名字
simpleCookie.setName("jizhuwo");
// 设置cookie的保存时间
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
8.2 将cookieSessionManager放入到安全管理器中
// 配置安全管理器
@Bean
public DefaultWebSecurityManager securityManager(
@Qualifier("myRealm")MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(ehCacheManager());
// 将session放入到安全管理器中
// securityManager.setSessionManager(sessionManager());
// 将cookie交给安全管理器
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
8.3 改造login.html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/login" method="post">
用户名:<input type="text" name="name"/><br />
密码: <input type="password" name="password" /><br />
记住我<input type="checkbox" name="rememberMe" /><br />
<input type="submit" value="点击提交">
</form>
</body>
</html>
8.4 改造controller
@RequestMapping(value = "login",method = RequestMethod.POST)
public String login( User user, Model model,boolean rememberMe){
// 获取token
UsernamePasswordToken token = new
UsernamePasswordToken(user.getName(), user.getPassword());
// 设置记住我这个功能
token.setRememberMe(rememberMe);
// 获取主体
Subject subject = SecurityUtils.getSubject();
// 登录
try {
subject.login(token);
} catch (UnknownAccountException e) {
log.info("用户名异常");
return "login";
} catch (IncorrectCredentialsException e){
log.info("密码错误");
return "login";
}
// 这个方法返回的数据其实就是realm中SimpleAuthenticationInfo中的第一个数据
String name = (String) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("name",name);
return "success";
}
8.5 将使用remembeMe功能以后能直接跳转的页面添加到过滤器中
// 配置过滤拦截请求
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 配置如果没有认证通过的话,跳转到这个页面
factoryBean.setLoginUrl("/toLogin");
// 如果没有访问权限,跳转到这个页面给以说明
factoryBean.setUnauthorizedUrl("/toshibai");
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 第一个表示的是路径,第二个参数表示的是过滤器名字
// login这个路径,允许请求
map.put("/login","anon");
map.put("/logout","logout");
// 设置哪些路径使用rememberMe功能之后刻印直接访问 user只是一种过滤器
map.put("/toAdd","user");
map.put("/toadd1","user");
// 要请求这个地址,必须有user:add权限
map.put("/toAdd","perms[user:ad]");
// 任何路径只有认证通过才可以访问
map.put("/**","authc");
factoryBean.setFilterChainDefinitionMap(map);
factoryBean.setSecurityManager(securityManager);
return factoryBean;
}
9. springboot整合多realm
9.1 导包
<!--导入shiro与spring整合的包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</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.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
9.2 编写全局配置
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5
9.3 编写对应的实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Admin {
private int id;
private String name;
private String password;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
9.4 编写对应的realm
public class AdminRealm extends AuthorizingRealm {
@Override
public String getName () {
return "AdminRealm";
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo ( PrincipalCollection principalCollection ) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo ( AuthenticationToken authenticationToken )
throws AuthenticationException {
String name = (String) authenticationToken.getPrincipal();
Admin admin = new Admin(1,"chaochao","123");
if (!name.equals("chaochao")){
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new
SimpleAuthenticationInfo(admin.getName(),
admin.getPassword(),
getName());
return simpleAuthenticationInfo;
}
}
public class UserRealm extends AuthorizingRealm{
@Override
public String getName () {
return "UserRealm";
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo (
AuthenticationToken authenticationToken )
throws AuthenticationException {
String name = (String) authenticationToken.getPrincipal();
User user = new User(1,"xiaowang","456");
if (!name.equals(user.getName())){
return null;
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user.getName(),
user.getPassword(),
getName()
);
return simpleAuthenticationInfo;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo ( PrincipalCollection principalCollection ) {
return null;
}
}
9.5 自定义token
public class CustomToken extends UsernamePasswordToken {
private String loginType;
public CustomToken(String name,String password,String loginType){
super(name,password);
this.loginType=loginType;
}
public void setLoginType(String loginType){
this.loginType=loginType;
}
public String getLoginType () {
return loginType;
}
}
9.6 自定义认证器
public class CusertomModularRealmeAuthticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate ( AuthenticationToken authenticationToken ) throws AuthenticationException {
// 做realm的一个校验
assertRealmsConfigured();
// 获取前端传过来的token
CustomToken customToken = (CustomToken) authenticationToken;
// 获取登录类型
String loginType = customToken.getLoginType();
// 获取所有的realms
Collection<Realm> realms = getRealms();
// 登录类型所对应的realm全部获取到
ArrayList<Realm> typeRealms = new ArrayList<>();
for (Realm realm:realms){
// 将获取到的realm类型和登录的realm类型进行对比
if (realm.getName().contains(loginType)){
typeRealms.add(realm);
}
}
if(typeRealms.size()==1){
return doSingleRealmAuthentication(typeRealms.iterator().next(),customToken);
}else {
return doMultiRealmAuthentication(typeRealms,customToken);
}
}
}
9.7 编写shiro配置文件
@SpringBootConfiguration
public class ShiroConfig {
// 编写过滤器
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(
@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean =
new ShiroFilterFactoryBean();
// 设置没有认证通过要去的页面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
Map<String,String> map = new LinkedHashMap<>();
map.put("/toLogin","anon");
map.put("/userLogin","anon");
map.put("/adminLogin","anon");
map.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
// 设置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
// 编写安全管理器的配置
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
Collection<Realm> realms = new ArrayList<>();
realms.add(adminRealm());
realms.add(userRealm());
// 设置认证器
securityManager.setAuthenticator(modularRealmeAuthticator());
securityManager.setRealms(realms);
return securityManager;
}
// realm的配置
@Bean
public AdminRealm adminRealm(){
AdminRealm adminRealm = new AdminRealm();
return adminRealm;
}
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
return userRealm;
}
// 注册校验器
public CusertomModularRealmeAuthticator modularRealmeAuthticator(){
CusertomModularRealmeAuthticator modularRealmeAuthticator =
new CusertomModularRealmeAuthticator();
return modularRealmeAuthticator;
}
}
9.8 编写一个枚举类,定义登录类型
public enum LoginType {
// enum 枚举:没有办法直接new,不是因为构造器被私有化,而是jvm解析这个枚举类的时候,
// 不允许new这个关键字来创建对象
// 2. 枚举没有子类,不允许被继承,也不允许被实现
// 3.也不能通过反射创建对象(单例模式虽然构造器私有化,但是可以通过反射一直创建这个类的实例))
USER("User"),ADMIN("Admin");
private String type;
private LoginType(String type){
this.type= type;
}
public String getType(){
return this.type.toString();
}
}
9.9 编写controller
@Controller
public class AdminController {
private final String LOGIN_TYPE= LoginType.ADMIN.getType();
@RequestMapping(value = "adminLogin",method = RequestMethod.POST)
public String adminToLogin( Admin admin){
CustomToken customToken = new CustomToken
(admin.getName(),admin.getPassword(),LOGIN_TYPE);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(customToken);
return "admincee";
} catch (UnknownAccountException e) {
System.out.println("用户名不对");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
return "adminLogin";
}
}
@Controller
public class UserController {
private final String LOGIN_TYPE= LoginType.USER.getType();
@RequestMapping(value = "userLogin",method = RequestMethod.POST)
public String adminToLogin( User user){
CustomToken customToken = new CustomToken
(user.getName(),user.getPassword(),LOGIN_TYPE);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(customToken);
return "usercee";
} catch (UnknownAccountException e) {
System.out.println("用户名不对");
}catch (IncorrectCredentialsException e){
System.out.println("密码错误");
}
return "adminLogin";
}
}
@Controller
public class OtherController {
@RequestMapping("toLogin")
public String toLogin(){
return "adminLogin";
}
}
9.10 编写html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是admin登录成功的页面
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是user登录成功的页面
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/adminLogin">
用户名:<input type="text" name="name">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>
<hr>
<form method="post" action="/userLogin">
用户名:<input type="text" name="name">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>