Shiro安全配置
主要还是整合了本地ehcache,集群session管理过段时间放出
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd"> <description>Shiro安全配置</description> <!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--设置自定义Realm-->
<property name="realm" ref="shiroDbRealm"/>
<!--将缓存管理器,交给安全管理器-->
<property name="cacheManager" ref="shiroEhcacheManager"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager" />
<!-- 记住密码管理 -->
<!-- <property name="rememberMeManager" ref="rememberMeManager"/> -->
</bean> <!-- 项目自定义的Realm -->
<bean id="shiroDbRealm" class="com.agood.bejavagod.shiro.ShiroDbRealm"/> <!-- 记住密码Cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<!-- 7天,采用spring el计算方便修改[细节决定成败]! -->
<property name="maxAge" value="#{7 * 24 * 60 * 60}"/>
<!-- <property name="domain" value=".bejavagod.com"/> -->
</bean> <!-- rememberMe管理器,cipherKey生成见{@code Base64Test.java} -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('5aaC5qKm5oqA5pyvAAAAAA==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean> <!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager"/>
<!-- 默认的登陆访问url -->
<property name="loginUrl" value="/login.action"/>
<!-- 登陆成功后跳转的url -->
<property name="successUrl" value="/index.action"/>
<!-- 没有权限跳转的url -->
<property name="unauthorizedUrl" value="/unauth.action"/> <!-- 自定义filter配置 -->
<property name="filters">
<map>
<entry key="authc">
<bean class="com.agood.bejavagod.controller.filter.CustomFormAuthenticationFilter"></bean>
</entry>
</map>
</property> <property name="filterChainDefinitions">
<value>
<!--
anon 不需要认证
authc 需要认证
user 验证通过或RememberMe登录的都可以
-->
<!-- /commons/** = anon -->
/static/** = anon
<!-- /webhooks = anon -->
/login.action = anon /page/404.action = anon
/page/500.action = anon <!-- /dataDict/saveOrUpdateDataDict.action = perms["shiro:save"] --> /** = authc
</value>
</property>
</bean> <!-- 用户授权信息Cache, 采用EhCache -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro/ehcache-shiro.xml"/>
</bean> <!-- 在方法中 注入 securityManager ,进行代理控制 -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <!-- 启用shrio授权注解拦截方式 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean> <!-- 会话管理器 -->
<!-- <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> -->
<!-- session的失效时长,单位毫秒 1小时: 3600000 -->
<!-- <property name="globalSessionTimeout" value="3600000"/> -->
<!-- 删除失效的session -->
<!-- <property name="deleteInvalidSessions" value="true"/> -->
<!-- </bean> --> <!-- 会话管理器 start -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 1小时: 3600000 -->
<!-- 设置全局会话超时时间,默认30分钟,即如果30分钟内没有访问会话将过期 1800000 -->
<property name="globalSessionTimeout" value="1800000"/>
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
<!-- 是否开启会话验证器,默认是开启的 -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
<!--
Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;
出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;
但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,
Shiro提供了会话验证调度器SessionValidationScheduler来做这件事情。
-->
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<!-- Shiro提供SessionDAO用于会话的CRUD -->
<property name="sessionDAO" ref="sessionDAO"/>
<!--
是否启用/禁用Session Id Cookie,默认是启用的;
如果禁用后将不会设置Session Id Cookie,即默认使用了Servlet容器的JSESSIONID,
且通过URL重写(URL中的“;JSESSIONID=id”部分)保存Session Id。
-->
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<!-- 会话验证调度器 -->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
<!-- 设置调度时间间隔,单位毫秒,默认就是1小时 -->
<property name="interval" value="1800000"/>
<!-- 设置会话验证调度器进行会话验证时的会话管理器 -->
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler"> -->
<!-- <property name="sessionValidationInterval" value="1800000"/> -->
<!-- <property name="sessionManager" ref="sessionManager"/> -->
<!-- </bean> -->
<!-- 会话DAO -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<!-- 设置Session缓存名字,默认就是shiro-activeSessionCache,要和ehcache.xml中的那么对应 -->
<property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
<!-- <property name="activeSessionsCacheName" value="shiroCache"/> -->
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 会话ID生成器,用于生成会话ID,默认就是JavaUuidSessionIdGenerator,使用java.util.UUID生成-->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 会话Cookie模板,sessionManager创建会话Cookie的模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- 设置Cookie名字,默认为JSESSIONID -->
<!-- <constructor-arg value="bjg_sid"/> -->
<!-- 不修改使用默认的话,那么404的时候session就会过期 -->
<property name="name" value="bjg_sid"/>
<!--
如果设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击;
此特性需要实现了Servlet 2.5 MR6及以上版本的规范的Servlet容器支持
-->
<property name="httpOnly" value="true"/>
<!-- 设置Cookie的过期时间,秒为单位,默认-1表示关闭浏览器时过期Cookie -->
<property name="maxAge" value="-1"/>
<!-- 设置Cookie的域名,默认空,即当前访问的域名 -->
<!-- <property name="domain" value=".bejavagod.com"/> -->
</bean>
<!-- 会话管理器 end --> <!-- 自定义form认证过虑器 -->
<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
<!-- <bean id="formAuthenticationFilter" class="com.agood.bejavagod.controller.filter.CustomFormAuthenticationFilter"> -->
<!-- 表单中账号的input名称 -->
<!-- <property name="usernameParam" value="username" /> -->
<!-- 表单中密码的input名称 -->
<!-- <property name="passwordParam" value="password" /> -->
<!-- 记住我input的名称 -->
<!-- <property name="rememberMeParam" value="rememberMe"/> -->
<!-- </bean> --> </beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd">
<description>Shiro安全配置</description>
<!--安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--设置自定义Realm-->
<property name="realm" ref="shiroDbRealm"/>
<!--将缓存管理器,交给安全管理器-->
<property name="cacheManager" ref="shiroEhcacheManager"/>
<!-- 注入session管理器 -->
<property name="sessionManager" ref="sessionManager"/>
<!-- 记住密码管理 -->
<!-- <property name="rememberMeManager" ref="rememberMeManager"/> -->
</bean>
<!-- 项目自定义的Realm -->
<bean id="shiroDbRealm" class="com.agood.bejavagod.shiro.ShiroDbRealm"/>
<!-- 记住密码Cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<!-- 7天,采用spring el计算方便修改[细节决定成败]! -->
<property name="maxAge" value="#{7 * 24 * 60 * 60}"/>
<!-- <property name="domain" value=".bejavagod.com"/> -->
</bean>
<!-- rememberMe管理器,cipherKey生成见{@code Base64Test.java} -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('5aaC5qKm5oqA5pyvAAAAAA==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- Shiro Filter -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager"/>
<!-- 默认的登陆访问url -->
<property name="loginUrl" value="/login.action"/>
<!-- 登陆成功后跳转的url -->
<property name="successUrl" value="/index.action"/>
<!-- 没有权限跳转的url -->
<property name="unauthorizedUrl" value="/unauth.action"/>
<!-- 自定义filter配置 -->
<property name="filters">
<map>
<entry key="authc">
<bean class="com.agood.bejavagod.controller.filter.CustomFormAuthenticationFilter"></bean>
</entry>
</map>
</property>
<property name="filterChainDefinitions">
<value>
<!--
anon 不需要认证
authc 需要认证
user 验证通过或RememberMe登录的都可以
-->
<!-- /commons/** = anon -->
/static/** = anon
<!-- /webhooks = anon -->
/login.action = anon /page/404.action = anon /page/500.action = anon
<!-- /dataDict/saveOrUpdateDataDict.action = perms["shiro:save"] -->
/** = authc
</value>
</property>
</bean>
<!-- 用户授权信息Cache, 采用EhCache -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:shiro/ehcache-shiro.xml"/>
</bean>
<!-- 在方法中 注入 securityManager ,进行代理控制 -->
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
<property name="arguments" ref="securityManager"/>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- AOP式方法级权限检查 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/>
<!-- 启用shrio授权注解拦截方式 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
<!-- 会话管理器 -->
<!--<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
session的失效时长,单位毫秒 1小时: 3600000
<property name="globalSessionTimeout" value="3600000"/>
删除失效的session
<property name="deleteInvalidSessions" value="true"/>
</bean>-->
<!-- 会话管理器 start -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- session的失效时长,单位毫秒 1小时: 3600000 --><!-- 设置全局会话超时时间,默认30分钟,即如果30分钟内没有访问会话将过期 1800000 -->
<property name="globalSessionTimeout" value="1800000"/>
<!-- 删除失效的session -->
<property name="deleteInvalidSessions" value="true"/>
<!-- 是否开启会话验证器,默认是开启的 -->
<property name="sessionValidationSchedulerEnabled" value="true"/>
<!-- Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;
出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;
但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,
Shiro提供了会话验证调度器SessionValidationScheduler来做这件事情。 -->
<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
<!-- Shiro提供SessionDAO用于会话的CRUD -->
<property name="sessionDAO" ref="sessionDAO"/>
<!-- 是否启用/禁用Session Id Cookie,默认是启用的;
如果禁用后将不会设置Session Id Cookie,即默认使用了Servlet容器的JSESSIONID,
且通过URL重写(URL中的“;JSESSIONID=id”部分)保存Session Id。
-->
<property name="sessionIdCookieEnabled" value="true"/>
<property name="sessionIdCookie" ref="sessionIdCookie"/>
</bean>
<!-- 会话验证调度器 -->
<bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler">
<!-- 设置调度时间间隔,单位毫秒,默认就是1小时 -->
<property name="interval" value="1800000"/>
<!-- 设置会话验证调度器进行会话验证时的会话管理器 -->
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!-- <bean id="sessionValidationScheduler" class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
<property name="sessionValidationInterval" value="1800000"/>
<property name="sessionManager" ref="sessionManager"/>
</bean>-->
<!-- 会话DAO -->
<bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<!-- 设置Session缓存名字,默认就是shiro-activeSessionCache,要和ehcache.xml中的那么对应 -->
<property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
<!-- <property name="activeSessionsCacheName" value="shiroCache"/> -->
<property name="sessionIdGenerator" ref="sessionIdGenerator"/>
</bean>
<!-- 会话ID生成器,用于生成会话ID,默认就是JavaUuidSessionIdGenerator,使用java.util.UUID生成-->
<bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
<!-- 会话Cookie模板,sessionManager创建会话Cookie的模板 -->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- 设置Cookie名字,默认为JSESSIONID -->
<!-- <constructor-arg value="bjg_sid"/> -->
<!-- 不修改使用默认的话,那么404的时候session就会过期 -->
<property name="name" value="bjg_sid"/>
<!-- 如果设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击;
此特性需要实现了Servlet 2.5 MR6及以上版本的规范的Servlet容器支持
-->
<property name="httpOnly" value="true"/>
<!-- 设置Cookie的过期时间,秒为单位,默认-1表示关闭浏览器时过期Cookie -->
<property name="maxAge" value="-1"/>
<!-- 设置Cookie的域名,默认空,即当前访问的域名 -->
<!-- <property name="domain" value=".bejavagod.com"/> -->
</bean>
<!-- 会话管理器 end -->
<!-- 自定义form认证过虑器 -->
<!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
<!-- <bean id="formAuthenticationFilter" class="com.agood.bejavagod.controller.filter.CustomFormAuthenticationFilter">
表单中账号的input名称
<property name="usernameParam" value="username"/>
表单中密码的input名称
<property name="passwordParam" value="password"/>
记住我input的名称
<property name="rememberMeParam" value="rememberMe"/>
</bean>-->
</beans>
Shiro安全配置的更多相关文章
- 跟开涛老师学shiro -- INI配置
之前章节我们已经接触过一些INI配置规则了,如果大家使用过如spring之类的IoC/DI容器的话,Shiro提供的INI配置也是非常类似的,即可以理解为是一个IoC/DI容器,但是区别在于它从一个根 ...
- shiro.ini 配置详解
引用: [1]开涛的<跟我学shiro> [2]<SpringMVC整合Shiro> [3]<shiro简单配置> [4]Apache shiro集群实现 (一) ...
- Shiro ini配置
Shiro.ini配置: ini配置文件类似Java中的properties(key = value),不过提供了key/value分类的特性,每个部分的key不重复即可 在eclipse中设置打开方 ...
- 简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)
前言: shiro因为其简单.可靠.实现方便而成为现在最常用的安全框架,那么这篇文章除了会用简洁明了的方式讲一下基于spring的shiro详细配置和登录注销功能使用之外,也会根据惯例在文章最后总结一 ...
- 解决Spring Boot集成Shiro,配置类使用Autowired无法注入Bean问题
如题,最近使用spring boot集成shiro,在shiroFilter要使用数据库动态给URL赋权限的时候,发现 @Autowired 注入的bean都是null,无法注入mapper.搜了半天 ...
- Shiro简单配置
注:这里只介绍Spring配置模式. 因为官方例子虽然中有更加简洁的ini配置形式,但是使用ini配置无法与spring整合.而且两种配置方法一样,只是格式不一样. 涉及的jar包 核心包shiro- ...
- [转] shiro简单配置
shiro(1) 注:这里只介绍spring配置模式. 因为官方例子虽然中有更加简洁的ini配置形式,但是使用ini配置无法与spring整合.而且两种配置方法一样,只是格式不一样. 涉及的jar包 ...
- Shiro框架配置-applicationContext里面的(仅提供借鉴)
<!-- 配置自定义realm --> <bean id="shiroAuthRealm" class="com.sykj.realm.ShiroAut ...
- shiro简单配置 (写的不错 收藏一下)
抄袭的连接:https://blog.csdn.net/clj198606061111/article/details/24185023 注:这里只介绍spring配置模式. 因为官方例子虽然中有更加 ...
随机推荐
- python基本数据类型之操作
python注释 当行注视:# 被注释内容多行注释:""" 被注释内容 """ 字符串操作 # 代表单行注释 ...
- 019-Spring Boot 日志
一.概述 spring-boot默认支持info级别的日志. 日志级别:trace.debug.info.warn.error.fatal.off[关闭] 二.配置日志级别 2.1.配置文件配置日志级 ...
- C语言中auto,register,static,const,volatile的区别
1)auto 这个关键字用于声明变量的生存期为自动,即将不在任何类.结构.枚举.联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量.这个关键字不怎么多写,因为所有的变量默认就是aut ...
- Linux用户和用户组管理 用户配置和管理的相关文件
用户信息文件 /etc/passwd 这个文件中保存的就是系统中所有的用户及其对应的用户主要信息. 文件格式 : 第1字段 第2字段 第3字段 第4字段 第5字段 第6字段 第7字段 用户名称 密码 ...
- ETL应用:一种一次获取一个平台接口文件的方法
ETL应用场景中,若对端接口文件未能提供,任务会处于循环等待,直到对端提供为止,该方法极大的消耗了系统资源.为此想到了一种方法,一次获取一个平台的文件,实现思路如下: 1.第一次获取对端平台提供目录下 ...
- Qt5.3.0的安装与测试
Qt5.3.0的安装与测试(交叉编译,用于arm,支持tslib触摸屏) 本次移植可以使用触摸屏. 首先下载源码包: http://download.qt.io/official_releases/q ...
- HTML5_CSS3实现iOS Path菜单
在线演示 本地下载
- Linux的压缩命令(tar,gzip,zip)
打包和压缩.打包是指将一大堆文件或目录变成一个总的文件:压缩则是将一个大的文件通过一些压缩算法变成一个小文件. 这源于Linux中很多压缩程序只能针对一个文件进行压缩,这样当你想要压缩一大堆文件时,你 ...
- matplotlib模块之子图画法
一般化的子图布局 首先要创建各个子图的坐标轴,传入一个四元列表参数:[x,y,width,height],用来表示这个子图坐标轴原点的x坐标.y坐标,以及宽和高.值得注意的是,这四个值的取值范围都是[ ...
- INSPIRED启示录 读书笔记 - 第36章 可用性与美感
两者缺一不可 交互设计和视觉设计完全是两回事 视觉设计可以满足用户的情感需求 良好的用户体验是交互设计师和视觉设计师合作的结果.他们共同配合产品经理定义产品