学习的spring boot 2.0的记录(十一):SpringBoot关于Shiro的配置(简单的登录demo)_Gin_Zou的博客-程序员宅基地

技术标签: springboo2.0  java  shiro  springboot  

前言:

学习的地址:https://www.majiaxueyuan.com/front/showcoulist 、https://www.bilibili.com/video/av44084437
SpringBoot的pom依赖(以2.0版本为例的)
本集记录的是shiro的权限框架(先写一个简单的demo,后面记录一个比较偏向实战的demo)

使用了springboot + freemark + shiro 这样的框架

作用如下:

piao的图:

都是基于在Security manager上进行的操作

整个demo的层次:

 

目录

 

目录

1.添加pom文件

2.编写配置application.properites文件

3.写一个简单的登录demo

异常:Uncaught SyntaxError: Unexpected token <

4.退出


 


1.添加pom文件

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 引入freeMarker的依赖包. -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>


        <!-- shiro -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

2.编写配置application.properites文件

这里是用于把freemark的一些配置以及常用的配置加载进去

server.port=8080
server.tomcat.uri-encoding=UTF-8
spring.freemarker.allow-request-override=false
spring.freemarker.cache=true
spring.freemarker.charset=UTF-8
spring.freemarker.check-template-location=true
spring.freemarker.content-type=text/html
spring.freemarker.expose-request-attributes=false
spring.freemarker.expose-session-attributes=false
spring.freemarker.expose-spring-macro-helpers=false
spring.freemarker.suffix=.ftl
spring.freemarker.template-loader-path=classpath:/templates/

 

3.写一个简单的登录demo

1.配置shiro类

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class ShiroConfiguration {
    // ShiroFilterFactoryBean 处理拦截问题,核心配置
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 放开请求
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/userLogin", "anon");
        filterChainDefinitionMap.put("/toErrorLogin", "anon");
        // 其他的请请求拦截
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 未授权界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/login");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

     // 这是去加载缓存的配置文件
    @Bean
    public EhCacheManager getEhCacheManager() {
        EhCacheManager em = new EhCacheManager();
        em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
        return em;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }

    // 配置org.apache.shiro.web.session.mgt.DefaultWebSessionManager session的配置
    @Bean
    public DefaultWebSessionManager getDefaultWebSessionManager() {
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionDAO(getMemorySessionDAO());
        //设置到最长的有效时间为1小时
        defaultWebSessionManager.setGlobalSessionTimeout(1 * 60 * 60 * 1000);
        defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
        defaultWebSessionManager.setSessionIdCookieEnabled(true);
        defaultWebSessionManager.setSessionIdCookie(getSimpleCookie());
        return defaultWebSessionManager;
    }

    // 配置org.apache.shiro.session.mgt.eis.MemorySessionDAO 配置唯一的UUID
    @Bean
    public MemorySessionDAO getMemorySessionDAO() {
        MemorySessionDAO memorySessionDAO = new MemorySessionDAO();
        memorySessionDAO.setSessionIdGenerator(javaUuidSessionIdGenerator());
        return memorySessionDAO;
    }

    @Bean
    public JavaUuidSessionIdGenerator javaUuidSessionIdGenerator() {
        return new JavaUuidSessionIdGenerator();
    }

    // session自定义cookie名
    @Bean
    public SimpleCookie getSimpleCookie() {
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName("security.session.id");
        simpleCookie.setPath("/");
        return simpleCookie;
    }

    @Bean
    public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm) {
        DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
        dwsm.setRealm(userRealm);
        // <!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
        dwsm.setCacheManager(getEhCacheManager());
        dwsm.setSessionManager(getDefaultWebSessionManager());
        return dwsm;
    }

    @Bean
    public UserRealm userRealm(EhCacheManager cacheManager) {
        UserRealm userRealm = new UserRealm();
        userRealm.setCacheManager(cacheManager);
        return userRealm;
    }

    // 开启shrio注解支持  可以不用 @Component
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(UserRealm userRealm) { // 使用的UserRealm
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(getDefaultWebSecurityManager(userRealm));
        return aasa;
    }
}

这个类具体就是在处理请求的 除了修改访问的路径,其他几乎都可以不动


2020年3月16日更:

注意:前台使用到的js资源也需要放置到过滤条件里面 否则会报错

Uncaught SyntaxError: Unexpected token <

必须这样:

  /*静态资源也需要通过 否则会有问题*/
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");

2.然后将需要的缓存配置文件放进去

<ehcache updateCheck="false" name="cacheManagerConfigFile">
	<defaultCache maxElementsInMemory="10000" eternal="false"
		timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="false"
		diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU" />
	<!-- 登录记录缓存 锁定1分钟 -->
	<!-- <cache name="lgoinRetryCache" maxEntriesLocalHeap="2000" eternal="false" 
		timeToIdleSeconds="60" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> 
		</cache> -->
	<cache name="shiro-activeSessionCache" eternal="false"
		maxElementsInMemory="10000" overflowToDisk="false" timeToIdleSeconds="0"
		timeToLiveSeconds="0" statistics="true" />
</ehcache>

将其放到resources根目录下

3.需要配置一个UserRealm类 来处理用户的授权和认证(暂时用不到授权,只记录了认证,并且在shiroconfiguration中也需要使用这个类)

package realm;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Collection;

/**
 * @author : HYXT_ZouQiJun
 * @createTime : 2019/7/31 17:03
 * @descrption :
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private SessionDAO sessionDAO;
    
    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        return null;
    }


    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        String username = (String) authenticationToken.getPrincipal(); //传递过来的username 就是登陆的用户名称

        String dbpassWord = "123"; //这里模拟的是数据库的用户密码
        System.out.println("开始进入到Realm:" + username);

        //这里是处理某处登陆后,当前登录失效的方法,就像异地登录那样
        Collection<Session> sessions = sessionDAO.getActiveSessions();
        for (Session session : sessions) {
            String loginedUserName = String.valueOf(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY));
            if (username.equals(loginedUserName)) {
                //说明已经是登陆成功的了。需要挤掉
                session.setTimeout(0L);
                break;
            }
        }

        SimpleAuthenticationInfo simpleAuthorizationInfo = null;

        //这里是将username 和 数据库得到的密码放进去 最后一个参数可以写死
        //实战是在数据库查到数据后将这个数据放进去
        //后期还可以加盐
        simpleAuthorizationInfo = new SimpleAuthenticationInfo(username, dbpassWord, "realName");
        return simpleAuthorizationInfo;
    }
}

4.写controller去处理请求数据

@Controller
public class LoginController {

 
    @RequestMapping("login")
    public String getLogin() {
        return "login";
    }

    @RequestMapping("userLogin")
    public String userLogin(@RequestParam("username") String userName, @RequestParam("password") String password, Map<String, String> maps) {

        if (StringUtils.isBlank(userName) || StringUtils.isBlank(password)) {
            System.out.println("帐号为空或者密码为空");
            return "redirect:/toErrorLogin";
        }
        //这里是将得到的用户和密码放到token中
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, password);
        //login方法到UserRealm的认证方法去
        subject.login(usernamePasswordToken);
            //如果是正常的,就会返回到一个主页
        return "redirect:/index/getIndex";
    }


    @RequestMapping("toErrorLogin")
    public String toErrorLogin(Map<String, String> maps) {
        System.out.println("我怀疑是这里");
        maps.put("code", "101");
        maps.put("msg", "用户和密码有问题");
        return "errorLogin";
    }

    @RequestMapping("logout")
    public String logout(Map<String, String> maps) {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        maps.put("msg", "退出成功");
        return "logout";
    }
}

indexController:

@Controller
@RequestMapping("/index")
public class IndexController {

    @RequestMapping("getIndex")
    public String getIndex(Map<String, String> map) {
        Subject subject = SecurityUtils.getSubject();
        String username = (String) subject.getPrincipal();
        map.put("name", username);
        return "index";
    }

    @RequestMapping("/toLogout")
    public String togetLogout(){
        return "redirect:/logout";
    }


}

5.可以写一个全局拦截异常类去处理异常 将其返回到重新登录的页面:

@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(AuthenticationException.class)
    public String ExceptionHandler() {
        System.out.println("抓取到异常了...");
        return "redirect:/toErrorLogin";
    }
}

6.把前端页面放到resources的templates中

index.ftl:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/html">
<head lang="en">
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
你好啊:${name}
</br>

退出:
<form action="toLogout" method="post">
    <input type="submit" name="logout" value="退出"/><br/>
</form>

</body>
</html>

login.ftl

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
骚年 登录 SAO么

<form action="userLogin" method="post">
    <input type="text" name="username"/><br/>
    <input type="password" name="password"/><br/>
    <input type="submit" name="submit"/><br/>
</form>
</body>
</html>

errorLogin.ftl:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
骚年 登录 SAO么????
${code}
${msg}
<form action="userLogin" method="post">
    <input type="text" name="username"/><br/>
    <input type="password" name="password"/><br/>
    <input type="submit" name="submit"/><br/>
</form>
</body>
</html>

还有一个登出的页面 logout.ftl

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
${msg}
<form action="login" method="post">
    <input type="submit" name="登录页面" value="跳转到登录页面......"/><br/>
</form>
</body>
</html>

当前的demo整个流程就是:

controller获得的请求之前基于aop会判断是否能够直接进入index页面

如果没有登陆的话全部会跳转到login或者errorlogin页面

输入的用户名密码会进入realm里面进行一个判断,如果是正确的会按正常流程处理。如果有误会抛出异常

AuthenticationException

所以这个时候使用拦截器去抓取然后在做其他处理

 

可以看看如下的demo流程:

1.在没有登录的情况下访问主页

http://localhost:9090/index

会自动跳转到登录页面

我们随便来个帐号密码  uname:姬子阿姨  pwd:111

因为后台写死的密码是123

所以111是不对的

因此会在拦截器中获取到异常:

然后重新跳转到toErrorLogin的方法中去

后台日志打印如下:

再测试一个正确的 密码是123

这就是正确的了

点击退出就是执行退出操作。

会将当前的用户登出 让其无法直接进入index

 

4.退出

其实很简单,在退出的controller中 将subject logout就行:格式如下:

User user = (User) o;
            log.info("user:" + user.getUserName());
            if ("websiteAdmin".equals(user.getUserName())) {
                httpServletRequest.getSession().removeAttribute("admin");
                /*shiro 进行用户登出,避免出现超权限的情况*/
                Subject subject = SecurityUtils.getSubject();
                subject.logout();
                log.info("当前用户已退出了");
                log.info(httpServletRequest.getRemoteHost() + "退出了");

            }

简单的demo如上。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28198181/article/details/97929008

智能推荐

图像压缩论文GENERALIZED OCTAVE CONVOLUTIONS FOR LEARNED MULTI-FREQUENCY IMAGE COMPRESSION阅读笔记__,_的博客-程序员宅基地

原文链接:http://arxiv.org/abs/2002.10032abstract基于深度学习的图像压缩最近显示出优于标准编解码器的潜力。最先进的率失真(R-D)性能是通过上下文自适应熵编码方法实现的,在这种方法中,超先验和自回归模型被联合用于有效地捕获潜在表示中的空间依赖性。然而,在以前的工作中,特征图是具有相同空间分辨率的特征映射,其中包含一些影响R-D性能的冗余。在本文中,我们提出了第一种学习到的多频图像压缩和熵编码方法,该方法基于最近开发的octave卷积,将特征图分解为高频和低频(分_图像压缩论文

UE4每日学习笔记_AI篇(四)_ue4设置黑板键值_老夫写代码就是一把梭的博客-程序员宅基地

EQS系统前期准备1.创建以下几个类文件1_1>创建必要的文件其中EQC_C是继承自EnvQueryContextBlueprintBase的类1_2>对EQC_C修改对这个方法重载,用一个玩家角色作为返回值2.对EQSAIController的操作2_1>先添加视觉组件2_2>重载AI感知中,当AI感知发生变化方法(视觉)代码解释:先保存看到的Actor,判断看到的Actor是否含有Player标签,并且是看到的情况下.._ue4设置黑板键值

Tensorrt7.2.1安装问题libcudnn.so.8: cannot open shared object file: No such file or directory_importerror: libcudnn.so.8: cannot open shared obj-程序员宅基地

最近安装TensorRT7.2.1,cuda版本10.2,cudnn8.02,按照官网教程安装完成之后,import tensorrt的时候遇到问题。ImportError: libcudnn.so.8: cannot open shared object file: No such file or directory我的cuda是做了一个软链接,软链接地址在/usr/local/cuda下,我首先将/usr/local/cuda/lib64的路径,添加到.bashrc里面,即export LD_importerror: libcudnn.so.8: cannot open shared object file: no such file or

智能楼宇安保监控系统实验实训_智慧安防实训系统教学-程序员宅基地

智能家居安防系统是传感技术、无线电技术、模糊控制技术等多种技术为一体的综合应用,利用现代的宽带信息网络和无线电网络平台,将家电控制、家庭环境控制、家庭监视监测、家庭安全防范、家庭信息交流、家庭娱乐、小区管理和服务集为一体构成的智能系统产品,是具有较强的技术性和前瞻性的新产品。这套系统包含门磁传感器、红外广角探测器、红外幕帘探测器等。智能安防监控五大系统1、防盗报警系统  防盗报警系统是通过安..._智慧安防实训系统教学

音乐推荐系统(协同过滤和SVD)_耿耿的星河的博客-程序员宅基地

python音乐推荐系统协同过滤和SVD矩阵分解_音乐推荐系统

E+H音叉液位开关FTL31-AA4M2AAWBJ_eh音叉开关ftl31接线图-程序员宅基地

E+H音叉液位开关FTL31-AA4M2AAWBJ振动式限位检测Liquiphant FTL31限位检测开关采用紧凑的卫生型设计和不锈钢外壳Liquiphant FTL31液体限位开关适用于所有行业测量,主要用于机械行业。Liquiphant可用于清洗、过滤系统以及冷却和润滑容器中的溢出保护和泵的空转保护。IO-Link 功能可轻松实现参数设置。紧凑:市场上最小尺寸的音叉传感器安全:连续自监控可靠开关动作,不受介质属性的影响简单:无需标定或调节即插即用IO-Link 连接过_eh音叉开关ftl31接线图

随便推点

Python自动登录网页网易云音乐-程序员宅基地

目标:在网页版网易云,以qq登录的方式,通过Python代码实现自动登录在实现过程中,关键的有2步: 1.在打开的页面中点击链接,打开了新页面。driver需要重定向到新页面,直接定位新界面的元素会提示,“定位失败,没有这个元素”。 解决方法:获取当前浏览器所有句柄页,根据句柄页的值进行转换。windows = driver.window_handles # 获取当前浏览器所有的页面句柄 windows的类型为list,通过windows[n...

属于计算机网络硬件系统有哪些,下列不属于计算机硬件系统的是()-程序员宅基地

相关题目与解析下列不属于计算机硬件系统的是()。下列不属于计算机网络硬件的是()以下不属于计算机硬件系统的是()。下列()不属于计算机的组成。A.软件B.硬件C.外部设备D.电源系统冯诺依曼结构计算机及特点:下列各项中不属于计算机硬件系统的主要组成部件的是()。下列各项中.不属于计算机硬件设备的是()。A.输入设备B.输出设备C.处理设备计算机硬件是计算机系统中各种物理装置的总称,以下不属于计算机..._列哪些不属于计算机安全中的硬件类危险:

转投go系列-Gin框架-ShouldBind验证参数问题_c.shouldbind-程序员宅基地

今天发现前人(php转go的选手)的代码通过SholdBind方法不验证参数直接通过。严重怀疑代码使用方式不正确导致。原来前人的代码是这么使用的 c.SholdBind…func (con *Controller) Add(c *gin.Context) { var addReq *AddReq if err := c.ShouldBind(&addReq); err != nil { rsp.Errno = 0 rsp.Errmsg = err.Error() c.JSON(h_c.shouldbind

OneNote2016和Visio 2016同时安装_onenote与viso冲突-程序员宅基地

软件下载1.微软官网下载office2016部署工具微软官网下载office2016部署工具:网址,或者上面提供的链接打开下载的部署工具,并将其释放到桌面右键单击office.iso文件加载至虚拟光驱用记事本或者sublime打开桌面上的configuration文件,用以下代码覆盖:&lt;Configuration&gt; &lt;Add SourcePath="V:\..._onenote与viso冲突

【杂谈】概率与随机以及手游抽卡机制的科普_抽奖prd算法-程序员宅基地

原文:NGA的一篇随机科普,其中包含了对手游抽卡机制的探讨。本文摘选了我自己感兴趣的部分。真随机先说点题外话,请先看这个问题一杯热水和一杯冷牛奶哪个热量更高?很显然这个问题从物理学和营养学的层面会得出相反的答案,( 先不考虑物理学层面说“一杯热水的热量”实际上是错误的 ),而关于“随机”的问题上的大部分疑惑与争论都恰如这个问题一般:扔硬币算不算真随机?计算机是否只能生成伪随机?伪随机是不是就一定能找到规律?想要解决这些问题,就必须从两个层面去理解“随机”,接着看下面两句话:1.让_抽奖prd算法

java 缓存数据字典,JAVA项目字典与缓存搭配使用方法解析_dfz1108的博客-程序员宅基地

JAVA项目字典与缓存搭配使用方法解析字典数据是什么?这里说了字典不是软件工程中说的数据字典。字典数据:用于动态设置某对象的属性是属于在一个可列举的数据范围内的某一类型数据,使用字典类型作为该类别属性的唯一标识如:用户类型:学生,老师,家长,校长等有什么作用?字典数据由两个表组成:字典类型表 dict_type,字典类型数据表 dict_data这个数据相当于java中的枚举类,可以被使用于前端的..._系统字典和缓存的区别