1 shiro
- Apache Shiro 是 Java 的一个安全(权限)框架。
- Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。
- Shiro 可以完成:认证、授权、加密、会话管理、与Web 集成、缓存等。
功能
-
Authentication:身份认证/登录,验证用户是不是拥有相应的身份
-
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限
-
Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web 环境的
-
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
-
Web Support:Web 支持,可以非常容易的集成到Web 环境
-
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率
-
Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去
-
Testing:提供测试支持
-
“Run As”:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
-
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
角色
核心三大对象:用户Subject, 管理用户SecurityManager, 连接数据Realms
- Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
- SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
- Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
2 快速了解
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute( "someKey", "aValue" );
if ( !currentUser.isAuthenticated() ) {
//collect user principals and credentials in a gui specific manner
//such as username/password html form, X509 certificate, OpenID, etc.
//We'll use the username/password example here since it is the most common.
//(do you know what movie this is from? ;)
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//this is all you have to do to support 'remember me' (no config - built in!):
token.setRememberMe(true);
currentUser.login(token);
}
try {
currentUser.login( token );
//if no exception, that's it, we're done!
} catch ( UnknownAccountException uae ) {
//username wasn't in the system, show them an error message?
} catch ( IncorrectCredentialsException ice ) {
//password didn't match, try again?
} catch ( LockedAccountException lae ) {
//account for that username is locked - can't login. Show them a message?
}
... more types exceptions to check if you want ...
} catch ( AuthenticationException ae ) {
//unexpected condition - error?
}
log.info( "User [" + currentUser.getPrincipal() + "] logged in successfully." );
3 使用
简单的源码:https://gitee.com/lujiachen/web-security-manger.git
1 依赖引入
<dependencies>
<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>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-web-starter -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.5.3</version>
</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>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2 配置
spring:
thymeleaf:
cache: false
datasource:
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: root
url: jdbc:mysql://localhost:3306/shiroadmin?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3 userRealm
import com.mysql.jdbc.log.Log;
import com.shiro.enity.User;
import com.shiro.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
@Slf4j
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 进入被拦截的url,就会进这个info
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 授权应该从数据库查出权限字段
// info.addStringPermission("user:add");
// 从 new SimpleAuthenticationInfo(queryUser, queryUser.getPwd(), "");传递过来第一个参数user最为subject
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
// 从数据库中获取验证权限
String[] split = currentUser.getPerms().split(",");
for (String s : split) {
info.addStringPermission(s);
}
log.info("进入授权=》"+currentUser.getPerms());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 模拟数据库中查出用户名、密码
UsernamePasswordToken userToken = (UsernamePasswordToken) token;
User currentUser = userService.queryUserByName((String)userToken.getUsername());
// 验证用户名
if (currentUser == null) {
// 用户名不正确,就抛出UnknownAccountException
return null;
}
log.info("password=>",currentUser.getPassword());
// 密码验证,shiro完成,不需要用户判断.直接返回
return new SimpleAuthenticationInfo(currentUser, currentUser.getPassword(), "");
}
}
4 shiroConfig
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.mgt.WebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("webSecurityManager") WebSecurityManager manager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
// 添加shiro的内置过滤器
/*
anon: 无需认证就可以登录
authc:必须认证才能登录
user: 必须拥有“记住我”这个功能
perms:拥有对某个资源的权限才能访问
role:拥有某个角色才能访问
*/
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 权限授权,访问url需要权限,支持通配符
map.put("/level1/*", "perms[user:add]");
map.put("/level2/*", "perms[all]");
map.put("/level3/*", "authc");
bean.setFilterChainDefinitionMap(map);
// 设置未授权的请求
bean.setUnauthorizedUrl("/noAuth");
// 设置登录url映射
bean.setLoginUrl("/toLogin");
return bean;
}
@Bean
public DefaultWebSecurityManager webSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm);
return manager;
}
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}