Springboot整合shiro

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>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值