0%


What is Apache Shiro

ApacheShiro™是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地获得任何应用程序——从最小的移动应用程序到最大的Web和企业应用程序。

Apache Shiro Features

Apache Shiro是具有许多功能的全面的应用程序安全框架。下图显示了Shiro集中精力的地方
shiro
Shiro以Shiro开发团队所谓的“应用程序安全性的四个基石”为目标-身份验证,授权,会话管理和密码学:

  • Authentication: 有时称为”登录”,这是证明用户是他们所说的身份的行为.
  • Authorization: 访问控制的过程,即确定“谁”有权访问”什么”.
  • Session Management:即使在非Web或EJB应用程序中,也可以管理用户特定的会话.
  • Cryptography: 使用密码算法保持数据安全,同时仍然易于使用。

shiro.ini

shiro.ini是shiro中的配置文件
以下是常见配置的解析
[main] 用于配置应用程序的SecurityManager实例及任何它的依赖组件(如realm)的地方.

1
2
3
4
[main]
myRealm=*.*.*.*realm
#依赖注入
securityManager.realm=$MyRealm

[users] 用于配置静态用户

1
2
3
[users]
zhangsan=1111
lisi=1111,role1,role2

[roles] 用于定义角色

1
2
3
4
[users]
zhangsan=1111,role1
[roles]
role1=user:add

Authentication

登录验证及异常捕获:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 1 创建SecurityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2 通过securityManager工厂获取securityManager实例
SecurityManager securityManager = factory.getInstance();
// 3 将securityManager对象设置到运行环境中
SecurityUtils.setSecurityManager(securityManager);
// 4 通过securityUtils获取subject
Subject subject = SecurityUtils.getSubject();
// 5 用户名 zhangsan 密码 1111 (表示用户登陆时输入的登录信息)
// shiro.ini的信息为数据库的信息
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","1111");
try {
// 进行用户身份验证
subject.login(token);
// 通过subject判断用户是否通过验证
if(subject.isAuthenticated()){
System.out.println("登录成功");
}
} catch (AuthenticationException e) { //可以进行详细捕获
e.printStackTrace();
System.out.println("登录失败");
}

关于异常的详细捕获 以下是AuthenticationException的子类关系,可根据不同的异常进行不同的处理.

  • AuthenticationException
    • AccountException
      • DisabledAccountException
        • LockedAccountException
      • ConcurrentAccessException
      • ExcessiveAttemptsException
      • UnknownAccountException
    • CredentialsException
      • IncorrectCredentialsException
      • ExpiredCredentialsException
    • UnsupportedTokenException

JDBC Realm

1 shiro完成认证的话默认使用的是ini realm,如需使用其他realm,则需要进行相关配置
2 使用jdbc realm来完成身份认证

使用jdbc realm主要是在shiro.ini配置realm

1
2
3
4
5
6
7
8
9
[main]
dataSource=com.mchange.v2.c3p0.ComboPooledDataSource
dataSource.driverClass=com.mysql.cj.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://localhost:3306/[数据库名]?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC
dataSource.user=[数据库账户名]
dataSource.password=[数据库密码]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSource
securityManager.realm=$jdbcRealm

值得一提的是mysql驱动为8.0以上的话,driverClass才为com.mysql.cj.jdbc.Driver
以前的版本的话则是com.mysql.jdbc.Driver
而且8.0+的url也必须加上一些设置,否则会抛出异常,无法连接到数据库.

Authentication Stratery

AuthenticationStratery是shiro中定义的验证多realm情况的验证策略的接口
它具有三种实现验证策略,基本上能解决大部分问题

FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略。
AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,将返回所有Realm身份校验成功的认证信息。
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份认证成功的认证信息,如果有一个失败就失败了。

关于多realm配置

1
securityManager.realms=$jdbcRealm,$jdbcRealm1

验证策略配置

1
2
3
4
5
6
7
8
# 配置验证策略
## eg
### authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
### authenticationStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
### authenticationStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
authenticationStrategy= org.apache.shiro.authc.pam.FirstSuccessfulStrategy
# 设置验证策略
securityManager.authenticator.authenticationStrategy=$authenticationStrategy

Custom Realm

有时jdbcRealm的局限性有些时候并不能支持我们完成需求,这个时候便需要自定义realm
自定义realm需要实现realm接口,shiro自身已经提供了一些实现类 eg. AuthenticatingRealm(实现了获取身份信息的功能)&AuthorizingRealm(实现了获取权限信息的功能).一般自定义realm会选择继承AuthorizingRealm,因为该类既提供了身份认证的自定义方法,也能实现授权的自定义方法.
example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyRealm extends AuthorizingRealm {

@Override
public String getName() {
return "myRealm";
}
//完成身份认证并返回认证信息,认证失败返回null
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户输入的用户名
String username = (String)token.getPrincipal();
System.out.println("username---->"+username);
//根据用户名到数据库查询密码信息
//todo... (jdbc)
// if return pwd=1111
String pwd = "1111";
//将查询的信息封装到SimpleAuthenticationInfo中
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,pwd,getName());
return info;
}
//授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}

shiro本身并不会维护数据,它只负责从相关接口接收传递来的数据进行验证授权流程.

Hash Algorithm

Hash algorithm也被称为散列算法,一般用于生成一段文本的摘要信息,散列算法不可逆,将内容可以生成摘要,无法将摘要转成原始内容.散列算法常用于对密码进行散列.在shiro中,也定义了一些常用的散列算法,eg:md2,md5,sha1,sha2(sha256,sha384,sha512).并且可以加入salt (一般散列算法需要提供一个salt(盐)与原始内容生成摘要信息,这样做的目的是为了安全性 ) ,以下是md5的常见用法.

1
2
3
4
5
6
7
8
9
//使用md5加密算法(salt)
Md5Hash md5Hash = new Md5Hash("1111");
System.out.println(md5Hash);
//加入salt(source,salt)
md5Hash = new Md5Hash("1111","xxx");
System.out.println(md5Hash);
//加入迭代次数(source,salt,hashIterations)
md5Hash = new Md5Hash("1111","xxx",2);
System.out.println(md5Hash);

自定义realm中使用hash algorithm
首先在自定义realm进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//完成身份认证并返回认证信息,认证失败返回null
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户输入的用户名
String username = (String)token.getPrincipal();
System.out.println("username---->"+username);

//根据用户名到数据库查询密码信息
//todo... (jdbc)

// if return pwd=1111(数据库返回pwd) & pwd salt=xxx & Iterations=2(shiro.ini中配置)
//进行md5加密赋值给pwd 此处省略new md5Hash("1111","xxx",2).toString();
String md5Hash = "2588ac6dd96981c619b6b6a94fb9ab59";
String salt = "xxx";

//将查询的信息封装到SimpleAuthenticationInfo中
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,md5Hash, ByteSource.Util.bytes(salt),getName());

return info;
}

shiro.ini配置

1
2
3
4
5
6
7
8
9
10
11
[main]
# 配置 credentialsMatcher
credentialsMatcher= org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
# 配置自定义realm
## eg: myRealm=com.xxx.realm.MyRealm
myRealm=MyRealm
myRealm.credentialsMatcher=$credentialsMatcher
# 设置realm
securityManager.realm=$myRealm

Authorization

  • 1 授权,也称为访问控制,是管理对资源的访问的过程。换句话说,控制谁可以访问应用程序。
  • 2 权限粒度:分为粗细两种粒度,shiro管理的是粗粒度的权限,细粒度的话一般需要结合业务进行设计,所以权限框架一般不会设计细粒度的权限管理.
  • 3 角色:含有相同权限的集合,方便管理.
  • 4 权限表示规则: 资源:操作:实例.可以用通配符表示:
    • user:add 表示对user有添加权限.
    • user:* 表示具有所有操作的权限.
    • user:delete:100 表示对user标识为100的记录具有删除的权限.
  • 5 shiro中的授权顺序
    shiro中的权限流程

步骤1:应用程序或框架代码调用任何的Subject hasRole,checkRole,isPermitted,或checkPermission方法的变体,传递是必需的任何许可或角色表示。

步骤2:该Subject情况下,典型地是DelegatingSubject(或子类)委托给应用程序的SecurityManager调用securityManager的几乎相同的各自hasRole,checkRole,isPermitted,或checkPermission方法变体(所述securityManager实现了org.apache.shiro.authz.Authorizer接口,它定义了所有特定主题授权方法)。

步骤3:本SecurityManager,作为一个基本的“伞”分量,继电器/委托给其内部org.apache.shiro.authz.Authorizer通过调用实例authorizer的各自hasRole,checkRole,isPermitted,或checkPermission方法。该authorizer实例默认情况下是一个ModularRealmAuthorizer实例,它支持Realm在任何授权操作期间协调一个或多个实例。

步骤4:Realm检查每个已配置的配置是否实现相同的Authorizer接口。如果是这样,该领域的各自的hasRole,checkRole,isPermitted,或checkPermission方法被调用。

  • 6 授权实现

编码实现

shiro.ini配置

1
2
3
[users]
zhangsan=1111,role1
lisi=1111,role2

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class AuthorizationDemo {
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan","1111");
try {
subject.login(token);
if(subject.isAuthenticated()){
System.out.println("认证成功");
//基于角色的授权判断
if(subject.hasRole("role1")){
System.out.println("授权成功");
}else{
System.out.println("授权失败");
}
//hasRoles(list)的授权判断
//subject.hasRoles(Arrays.asList("role1","role2"));
//checkRole的授权判断,授权失败则抛出UnauthorizedException异常,同样可使用checkRoles()判断多个角色
//subject.checkRole("role2");
//subject.checkRoles("role1","role2");
//基于资源的判断
//System.out.println(subject.isPermitted("user:select"));
//System.out.println(subject.isPermittedAll("user:*"));
//checkPermission判断 类似checkRole,授权失败也会抛出UnauthorizedException异常
//subject.checkPermission("user:add");
//subject.checkPermissions("user:*");
}
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("认证失败");
}
}
}

注解实现
注解结构

注解 意义 案例
@RequiresAuthentication 验证用户是否登录
@RequiresUser 当前用户已经验证过了或则记住我了
@RequiresGuest 是否是游客身份
@RequiresRoles 判断subject中有aRoleName角色才可以访问方法someMethod @RequiresRoles({“admin”})
@RequiresPermissions 需要拥有权限 @RequiresPermissions({“file:read”, “write:aFile.txt”} )

jsp标签实现

1
2
<%--shiro 标签 --%>
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
标签 意义
<shiro:authenticated> 登录之后
<shiro:notAuthenticated> 不在登录状态时
<shiro:guest> 用户在没有RememberMe时
<shiro:user> 用户在RememberMe时
<shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色时
<shiro:hasRole name="abc"> 拥有角色abc
<shiro:lacksRole name="abc"> 没有角色abc
<shiro:hasPermission name="abc"> 拥有权限资源abc
<shiro:lacksPermission name="abc"> 没有abc权限资源
<shiro:principal> 默认显示用户名称

关于自定义realm中的设置权限
可通过SimpleAuthorizationInfo类的方法进行权限或者角色的添加
最后返回info信息即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//授权信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = principalCollection.getPrimaryPrincipal().toString();
System.out.println(username);
List<String> permission = new ArrayList<String>();
permission.add("user:add");
permission.add("user:update");
permission.add("user:delete");
permission.add("user:find");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("role1");
for (String perms:permission){
info.addStringPermission(perms);
}
return info;
}

Integrations

shiro与ssm的整合

web.xml添加shiro配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!--shiro web.xml配置-->

<!--shiroFilter配置 通过代理配置 对象是spring容器创建的 交与servlet容器管理-->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<!--将bean生命周期交与servlet管理-->
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<!--表示在spring容器中bean的id交与servlet管理-->
<!--不配置默认值与filter的Name一致,此处配置只做示例-->
<param-name>targetBeanName</param-name>
<param-value>shiroFilter</param-value>
</init-param>
</filter>

<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

applicationContext.xml中添加shiro配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<?xml version='1.0' encoding='UTF-8'  ?>
<beans>
<!--shiro applicationContext配置-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--引用securityManager-->
<property name="securityManager" ref="securityManager"></property>
<!--登录url 在访问需要认证资源时未认证跳转到此url-->
<!--不配置时默认路径为/login.jsp-->
<property name="LoginUrl" value="/login"></property>
<!--认证成功url-->
<!--successUrl配置只是做为一种附加配置,只有session中没有用户请求地址时才会使用successUrl 所以一般不设置 默认是成功后跳转到上一个请求的url-->
<!--<property name="successUrl" ></property>-->
<!--配置用户没有权限访问资源时跳转的url-->
<property name="unauthorizedUrl" value="/refuse"></property>
<!--配置shiro过滤器链-->
<property name="filterChainDefinitions">
<value>
/toLogin=anon
/login=authc
/logout=logout
/**=anon
<!--静态资源-->
/css/**=anon
/js/**=anon
/images/**=anon
</value>
</property>
</bean>
<!--配置authc过滤器-->
<bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<!--表单中的name 默认为username password-->
<property name="usernameParam" value="name"></property>
<property name="passwordParam" value="pwd"></property>
</bean>
<!--配置logout过滤器-->
<bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
<!--默认为"/"-->
<property name="redirectUrl" value="/" />
</bean>
<!--配置securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--引用realm-->
<property name="realm" ref="userRealm"></property>
</bean>
<!--配置userRealm-->
<bean id="userRealm" class="com.xxx.realm.userRealm">
<!--引用凭证匹配器-->
<!--注意!引用凭证匹配器需要序列化userBean,同时realm需传入salt-->
<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
<!--配置凭证匹配器-->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="md5"/>
<property name="hashIterations" value="2"/>
</bean>
</beans>

loginController的/login方法配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//登录
@requestMapping("/login")
public static ModelAndView login(HttpServletRequest request){
ModelAndView mav = new ModelAndView("login");
String className=(String)request.getAttribute("shiroLoginFailure");
if(UnknownAccountException.class.getName().equals(className)){
//抛出自定义异常
mav.addObject("msg","用户名或密码错误");
}else if(IncorrectCredentialsException.class.getName().equals(className)){
//抛出自定义异常
mav.addObject("msg","用户名或密码错误");
}else{
mav.addObject("msg","未知错误");
}
return mav;
}

custom realm配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class UserRealm extends AuthorizingRealm {

@Override
public String getName() {
return "userRealm";
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal();
//通过services(调用mapper)方法根据用户名查询并返回user信息
//此处pwd为示例简写 正常则为返回的user.getPwd()方法得出
String pwd = "1111";

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,pwd,getName());
return info;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取用户对象
User user = principalCollection.getPrimaryPrincipal();
//根据对象查询角色 再查询出权限 此处略过
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// info.addStringPermissions();
return info;
}
}

spring mvc配置aop代理实现shiro授权

1
2
3
4
5
6
7
<!--spring-mvc.xml-->
<!--开启aop 对类代理-->
<aop:config proxy-target-class="true"></aop:config>
<!--添加shiro授权注解支持-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager">
</bean>

此时,如果授权不通过的话shiro并不会处理异常(认证也一样),需要在spring mvc中处理,具体配置如下

1
2
3
4
5
6
7
8
9
10
11
12
<!--shiro异常处理-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--key为异常完全限定名(包名+类名),值为视图名-->
<!--授权异常-->
<prop key="org.apache.shiro.authz.UnauthorizedEeception">refuse</prop>
<!--认证异常-->
<prop key="org.apache.shiro.authz.UnauthenticatedEeception">login</prop>
</props>
</property>
</bean>

添加ehcache缓存管理
shiro默认集成了ehcache配置,如需自定义需把配置文件放入src目录下

自定义配置文件

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8" ?>
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="128"
overflowToDisk="true"/>
</ehcache>

之后在applicationContext.xml中cacheManager的配置

1
2
3
4
5
6
7
8
9
10
11
12
<!--配置securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--引用realm-->
<property name="realm" ref="userRealm"></property>
<!--引用缓存管理器-->
<property name="cacheManager" ref="cacheManager"></property>
</bean>
<!--配置缓存管理器-->
<bean name="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!--添加了自定义配置才需加上-->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
</bean>

如果因为权限变更需要清理缓存则需在spring容器中调用realm中的清理缓存方法clearCache(SecurityUtils.getSubject().getPrincipals());
Session管理和rememberMe
sessionManager同样也是在ApplicationContext.xml的securityManger下的property中进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--配置securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--引用realm-->
<property name="realm" ref="userRealm"/>
<!--引用缓存管理器-->
<property name="cacheManager" ref="cacheManager"/>
<!--引用sessionManager-->
<property name="sessionManager" ref="sessionManager"/>
</bean>
<!--配置会话管理器-->
<bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
<!--单位ms-->
<property name="globalSessionTimeout" value="300000"/>
</bean>

rememberMe
首先在authc过滤器中添加

1
2
3
4
5
6
7
8
<!--配置authc过滤器-->
<bean id="authc" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
<!--表单中的name-->
<property name="usernameParam" value="name"/>
<property name="passwordParam" value="pwd"/>
<!--rememberMe-->
<property name="rememberMeParam" value="rememberMe"/>
</bean>

同样的,还需要在securityManager中引入rememberMeManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--配置securityManager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--引用realm-->
<property name="realm" ref="userRealm"/>
<!--引用缓存管理器-->
<property name="cacheManager" ref="cacheManager"/>
<!--引用sessionManager-->
<property name="sessionManager" ref="sessionManager"/>
<!--引用rememberMeManager-->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
<!--配置rememberMeManager-->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!--引用rememberMeCookie-->
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!--配置rememberMeCookie-->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!--cookie存活时间-->
<property name="maxAge" value="604800"/>
<!--设置cookie名称-->
<property name="name" value="rememberMe"/>
<!--需要完全序列化userBean(包括userBean引用的类也需要被序列化)-->
</bean>

同时在过滤器链将首页验证改为user

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--配置shiro过滤器链-->
<property name="filterChainDefinitions">
<value>
/toLogin=anon
/login=authc
/logout=logout
<!--使用该过滤器以实现记住我-->
/index=user
/**=authc
<!--静态资源-->
/css/**=anon
/js/**=anon
/images/**=anon
</value>
</property>

最后记得把记住我的checkbox的name改为rememberMe

最后

到这里shiro差不多可以告一段落了,其他的什么功能以后用到再加上来吧.


开始

前端时间Java13好像发布了,正好看到有篇文章记录了Java13中的5个特性,就拿过来水一下了. :)传送门.

Dynamic CDS Archives

这一特性是在JEP310:Application Class-Data Sharing基础上扩展而来的,Dynamic CDS Archives中的CDS指的就是Class-Data Sharing。

那么,这个JEP310是个啥东西呢?

我们知道在同一个物理机/虚拟机上启动多个JVM时,如果每个虚拟机都单独装载自己需要的所有类,启动成本和内存占用是比较高的。所以Java团队引入了CDS的概念,通过把一些核心类在每个JVM间共享,每个JVM只需要装载自己的应用类,启动时间减少了,另外核心类是共享的,所以JVM的内存占用也减少了。

CDS 只能作用于 Boot Class Loader 加载的类,不能作用于 App Class Loader 或者自定义的 Class Loader 加载的类。

在 Java 10 中,则将 CDS 扩展为 AppCDS,顾名思义,AppCDS 不止能够作用于 Boot Class Loader了,App Class Loader 和自定义的 Class Loader 也都能够起作用,大大加大了 CDS 的适用范围。也就说开发自定义的类也可以装载给多个JVM共享了。

Java 10中包含的JEP310的通过跨不同Java进程共享公共类元数据来减少了内存占用和改进了启动时间。

但是,JEP310中,使用AppCDS的过程还是比较复杂的,需要有三个步骤:

1、决定要 Dump 哪些 Class
2、将类的内存 Dump 到归档文件中
3、使用 Dump 出来的归档文件加快应用启动速度
这一次的JDK 13中的JEP 350 ,在JEP310的基础上,又做了一些扩展。允许在Java应用程序执行结束时动态归档类,归档类将包括默认的基础层 CDS(class data-sharing)存档中不存在的所有已加载的应用程序类和库类。

也就是说,在Java 13中再使用AppCDS的时候,就不在需要这么复杂了。

ZGC: Uncommit Unused Memory

在讨论这个问题之前,想先问一个问题,JVM的GC释放的内存会还给操作系统吗?

GC后的内存如何处置,其实是取决于不同的垃圾回收器的。因为把内存还给OS,意味着要调整JVM的堆大小,这个过程是比较耗费资源的。

在JDK 11中,Java引入了ZGC,这是一款可伸缩的低延迟垃圾收集器,但是当时只是实验性的。并且,ZGC释放的内存是不会还给操作系统的。
image

而在Java 13中,JEP 351再次对ZGC做了增强,本次 ZGC 可以将未使用的堆内存返回给操作系统。之所以引入这个特性,是因为如今有很多场景中内存是比较昂贵的资源,在以下情况中,将内存还给操作系统还是很有必要的:

  • 1、那些需要根据使用量付费的容器
  • 2、应用程序可能长时间处于空闲状态并与许多其他应用程序共享或竞争资源的环境。
  • 3、应用程序在执行期间可能有非常不同的堆空间需求。例如,启动期间所需的堆可能大于稍后在稳定状态执行期间所需的堆。

Reimplement the Legacy Socket API

使用易于维护和调试的更简单、更现代的实现替换 java.net.Socket 和 java.net.ServerSocket API。

java.net.Socket和java.net.ServerSocket的实现非常古老,这个JEP为它们引入了一个现代的实现。现代实现是Java 13中的默认实现,但是旧的实现还没有删除,可以通过设置系统属性jdk.net.usePlainSocketImpl来使用它们。

运行一个实例化Socket和ServerSocket的类将显示这个调试输出。这是默认的(新的):

1
2
3
4
5
6
7
8
9
10
11
12
13
java -XX:+TraceClassLoading JEP353  | grep Socket
[0.033s][info ][class,load] java.net.Socket source: jrt:/java.base
[0.035s][info ][class,load] java.net.SocketOptions source: jrt:/java.base
[0.035s][info ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.039s][info ][class,load] java.net.SocketImpl$$Lambda$1/0x0000000800b50840 source: java.net.SocketImpl
[0.042s][info ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base
[0.042s][info ][class,load] sun.nio.ch.NioSocketImpl source: jrt:/java.base
[0.043s][info ][class,load] sun.nio.ch.SocketDispatcher source: jrt:/java.base
[0.044s][info ][class,load] java.net.DelegatingSocketImpl source: jrt:/java.base
[0.044s][info ][class,load] java.net.SocksSocketImpl source: jrt:/java.base
[0.044s][info ][class,load] java.net.ServerSocket source: jrt:/java.base
[0.045s][info ][class,load] jdk.internal.access.JavaNetSocketAccess source: jrt:/java.base
[0.045s][info ][class,load] java.net.ServerSocket$1 source: jrt:/java.base

上面输出的sun.nio.ch.NioSocketImpl就是新提供的实现。

如果使用旧的实现也是可以的(指定参数jdk.net.usePlainSocketImpl):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ java -Djdk.net.usePlainSocketImpl -XX:+TraceClassLoading JEP353  | grep Socket
[0.037s][info ][class,load] java.net.Socket source: jrt:/java.base
[0.039s][info ][class,load] java.net.SocketOptions source: jrt:/java.base
[0.039s][info ][class,load] java.net.SocketImpl source: jrt:/java.base
[0.043s][info ][class,load] java.net.SocketImpl$$Lambda$1/0x0000000800b50840 source: java.net.SocketImpl
[0.046s][info ][class,load] sun.net.PlatformSocketImpl source: jrt:/java.base
[0.047s][info ][class,load] java.net.AbstractPlainSocketImpl source: jrt:/java.base
[0.047s][info ][class,load] java.net.PlainSocketImpl source: jrt:/java.base
[0.047s][info ][class,load] java.net.AbstractPlainSocketImpl$1 source: jrt:/java.base
[0.047s][info ][class,load] sun.net.ext.ExtendedSocketOptions source: jrt:/java.base
[0.047s][info ][class,load] jdk.net.ExtendedSocketOptions source: jrt:/jdk.net
[0.047s][info ][class,load] java.net.SocketOption source: jrt:/java.base
[0.047s][info ][class,load] jdk.net.ExtendedSocketOptions$ExtSocketOption source: jrt:/jdk.net
[0.047s][info ][class,load] jdk.net.SocketFlow source: jrt:/jdk.net
[0.047s][info ][class,load] jdk.net.ExtendedSocketOptions$PlatformSocketOptions source: jrt:/jdk.net
[0.047s][info ][class,load] jdk.net.ExtendedSocketOptions$PlatformSocketOptions$1 source: jrt:/jdk.net
[0.048s][info ][class,load] jdk.net.LinuxSocketOptions source: jrt:/jdk.net
[0.048s][info ][class,load] jdk.net.LinuxSocketOptions$$Lambda$2/0x0000000800b51040 source: jdk.net.LinuxSocketOptions
[0.049s][info ][class,load] jdk.net.ExtendedSocketOptions$1 source: jrt:/jdk.net
[0.049s][info ][class,load] java.net.StandardSocketOptions source: jrt:/java.base
[0.049s][info ][class,load] java.net.StandardSocketOptions$StdSocketOption source: jrt:/java.base
[0.051s][info ][class,load] sun.net.ext.ExtendedSocketOptions$$Lambda$3/0x0000000800b51440 source: sun.net.ext.ExtendedSocketOptions
[0.057s][info ][class,load] java.net.DelegatingSocketImpl source: jrt:/java.base
[0.057s][info ][class,load] java.net.SocksSocketImpl source: jrt:/java.base
[0.058s][info ][class,load] java.net.ServerSocket source: jrt:/java.base
[0.058s][info ][class,load] jdk.internal.access.JavaNetSocketAccess source: jrt:/java.base
[0.058s][info ][class,load] java.net.ServerSocket$1 source: jrt:/java.base

上面的结果中,旧的实现java.net.PlainSocketImpl被用到了。

Switch Expressions (Preview)

在JDK 12中引入了Switch表达式作为预览特性。JEP 354修改了这个特性,它引入了yield语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield, switch语句(不返回值)应该使用break。

在以前,我们想要在switch中返回内容,还是比较麻烦的,一般语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
int i;
switch (x) {
case "1":
i=1;
break;
case "2":
i=2;
break;
default:
i = x.length();
break;
}

在JDK13中使用以下语法:

1
2
3
4
5
6
7
8
int i = switch (x) {
case "1" -> 1;
case "2" -> 2;
default -> {
int len = args[1].length();
yield len;
}
};

或者

1
2
3
4
5
6
7
8
int i = switch (x) {
case "1": yield 1;
case "2": yield 2;
default: {
int len = args[1].length();
yield len;
}
};

在这之后,switch中就多了一个关键字用于跳出switch块了,那就是yield,他用于返回一个值。和return的区别在于:return会直接跳出当前循环或者方法,而yield只会跳出当前switch块。

Text Blocks (Preview)

在JDK 12中引入了Raw String Literals特性,但在发布之前就放弃了。这个JEP在引入多行字符串文字(text block)在意义上是类似的。

text block,文本块,是一个多行字符串文字,它避免了对大多数转义序列的需要,以可预测的方式自动格式化字符串,并在需要时让开发人员控制格式。

我们以前从外部copy一段文本串到Java中,会被自动转义,如有一段以下字符串:

1
2
3
4
5
<html>
<body>
<p>Hello, world</p>
</body>
</html>

将其复制到Java的字符串中,会展示成以下内容:

1
2
3
4
5
"<html>\n" +
" <body>\n" +
" <p>Hello, world</p>\n" +
" </body>\n" +
"</html>\n";

即被自动进行了转义,这样的字符串看起来不是很直观,在JDK 13中,就可以使用以下语法了:

1
2
3
4
5
6
7
"""
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";

使用”””作为文本块的开始符合结束符,在其中就可以放置多行的字符串,不需要进行任何转义。看起来就十分清爽了。

如常见的SQL语句:

1
2
3
4
5
String query = """
SELECT `EMP_ID`, `LAST_NAME` FROM `EMPLOYEE_TB`
WHERE `CITY` = 'INDIANAPOLIS'
ORDER BY `EMP_ID`, `LAST_NAME`;
""";

看起来就比较直观,清爽了。

最后

水完了 over!


开始

其实一直有体验Linux的想法,只是以前bios设了密码,后来给忘了,最近突然尝试猜了一下,居然对了!天意!双系统启动!
好,说整就整!

Ubuntu的安装

Ubuntu作为当红Linux发行版,好就它了!
本地是win10环境,上网找了很多win10+Ubuntu的双系统安装教程,好像还和是不是单硬盘、支不支持uefi有关,个人认为这个教程讲的十分详细—传送门
我这边是uefi+单硬盘,所以大概说一下,其他情况可以参考上面那篇博客。

1、在win10中分出一个空白分区留给Linux
win10专业版的话就是 此电脑右键-管理-磁盘管理 应该就能看到磁盘的各个分区,如果只有C盘的话直接压缩卷即可,如果自己分了cde的话就把最后那个分区删除卷也行,记得备份!记得备份!记得备份,我这边分了100g左右。
2、将Ubuntu镜像写入U盘
准备一个U盘,下载并安装软碟通,Ubuntu镜像直接在官网下载的桌面版18.04LTS。然后就是用软碟通把镜像刷入U盘,等就完事了。
3、安装Ubuntu
接下来,就是正常的BIOS设置U盘先加载,哦对了,还要关了sucure boot,我也不知道为啥,关就完事了。然后会进入Ubuntu的安装界面,直接选择安装,记得断网安装,不然的下载一个世纪的文件(泪)。安装的话有大概这么一些选项
3.1、语言选择(自己看着选)
3.2、安装 (有个在安装中下载更新的,记得断网,最小安装就行了)
3.3、安装类型(好像会提示和win10作为双系统安装,应该也没事,反正我点的自定义。)
3.4、分区(大佬说的 /boot 分200mb /home 35g 其他全给/ ,有些教程分区分了很多,反正我就这样分的,我8g内存,感觉应该够。就没分swap,后面发现自动分了2g,问题不大,赶紧进入系统才是王道)
之后就是系统的一些简单默认配置了,都能看懂,不多说了

初体验

安装完成之后就是拔U盘,重启!默认是grub引导双系统界面,应该能看到Ubuntu和windows,选择Ubuntu,开始!
对了 有些因为显卡原因,在Ubuntu重启会死机,在软件更新器点设置,有个附加驱动,会自动搜索可用驱动,装上驱动就好了,妈妈再也不用担心我死机了:)

简单配置

在瞎点了几下之后,差不多可以开始一些简单的配置了,比如换源,用阿里云就完事了,中文输入法,直接firefox搜搜狗Linux,有安装教程。下载的话好像挺多人用uget配合aria2下载,百度就完事了。对了如果下载jetbrains全家桶的话建议用toolbox,稳定,下载速度也可以。办公的话wps,听歌的话用网易云。qq用的deepin wine版本的,比原版wine好用那么一点—deepin wine传送门,美化的话,慢慢百度吧,怎么舒服怎么来。

最后

差不多了,接下来就是慢慢折腾了,养老的话也是很舒适的。

2019/9/27更新

ubuntu已被玩坏(gnome依赖可能出问题了…),转身投入deepin(15.11)的怀抱。
自带 chrome 搜狗输入法 wps 深度截图 网易云 qq等
开箱即用的体验 界面的话 有好有坏
好处:界面还算漂亮 dock支持win和默认方案 grub不算难看 省去了换refind的时间
坏处:字体渲染可能比Ubuntu差点(maybe)默认终端太丑了 美化的话 慢慢折腾吧
顺带说一下 开关机还有音效 太蠢了!

END

双系统引导关了,转身投入win怀抱,最后一次更新,Linux果然还是太折腾了,windows真香 :)


开始

新建的第一篇文章,就写写hexo的一些东西吧
hexo的本地安装到部署到github pages
还有主题,以及gitalk的配置

准备

hexo的安装十分方便,官网也讲的非常清楚,需要nodeJs和git环境,这里不再赘述,不清楚的可以查看官方文档
当准备工作完成之后,就可以直接使用npm来安装hexo

1
npm install -g hexo-cli

之后就是初始化的过程

1
2
3
hexo init <folder>
cd <folder>
npm install

配置

新建完成后,指定文件夹的目录如下:

1
2
3
4
5
6
7
8
.
├── _config.yml
├── package.json
├── scaffolds
├── source
| ├── _drafts
| └── _posts
└── themes

开始的话,我们需要关注的就是config.yml,这个是全局的一些配置文件.主题文件在themes文件夹,默认的主题是landspace,如果想切换主题可以去github搜索,或者直接查看hexo官方主题,一般的话更换主题都是将主题文件安装到themes文件夹下,然后在全局配置文件中更改主题.
至于评论系统的,这里使用的是gitalk(Gitalk 是一个基于 GitHub Issue 和 Preact 开发的评论插件。),为什么是gitalk呢?因为这个主题的配置文件就是配置的gitalk.其他主题配置的评论系统也许是别的,主题文档应该会有说明,可以根据文档自行配置.因为评论插件太多了,这个就自行百度吧.

新建博客

新建博客的话,使用hexo new命令即可,也可以直接在source/_posts文件夹下新建markdown文件直接编写.

1
hexo new (title)

如果想在部署前预览的话,使用hexo s命令即可通过 http://localhost:4000 查看效果.

1
hexo s

部署

部署到github pages的话,直接在全局配置文件config.yml修改deploy配置

1
2
3
4
deploy:
type: git
repo: https://github.com/github名称/github名称.github.io.git
branch: master

然后的话还需要安装一个插件才能部署

1
npm install hexo-deployer-git --save

之后执行以下命令即可部署到github pages

1
hexo g && hexo d

最后

接下来就是愉快的水博客环节~