目录
一、认识Shiro
1、什么是Shiro?
- Apache Shiro 是一个Java 的安全(权限)框架。
- Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环
境。 - Shiro可以完成,
认证
,授权
,加密
,会话管理
,Web集成
,缓存
等。
2、有哪些功能?
Authentication
:身份认证、登录,验证用户是不是拥有相应的身份;Authorization
:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否
进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否
具有某个权限!Session Manager
:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都
在会话中;会话可以是普通的JavaSE环境,也可以是Web环境;Cryptography
:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储;Web Support
:Web支持,可以非常容易的集成到Web环境;Caching
:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高
效率Concurrency
:Shiro支持多线程应用的并发验证,即,如在一个线程中开启另一个线程,能把权限
自动的传播过去Testing
:提供测试支持;Run As
:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;Remember Me
:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了
3、Shiro架构(外部)
Subject
:代表当前"用户"。与当前应用程序交互的任何东西都是Subject,如爬虫、机器人。所有的Subject 都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager。Subject 是一个门面,SecurityManager 是实际的执行者。SecurityManager
:与安全有关的操作都会与SecurityManager交互。它管理者所有Subject,是Shiro的核心,负责与其他组件进行交互。Realm
: Shiro从Realm中获取安全数据(用户、角色、权限)。SecurityManager需要从Realm中获取响应的用户信息进行比较用户身份是否合法,也需要从Realm中得到用户相应的角色/权限进行验证,以确定用户是否能够操作。
4、Shiro架构(内部)
Subject
:任何可以与应用交互的用户
;Security Manager
:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏,所有具体的交互
都通过Security Manager进行控制,它管理者所有的Subject,且负责进行认证,授权,会话,及
缓存的管理。Authenticator
:负责Subject认证,是一个扩展点,可以自定义实现;可以使用认证策略
(Authentication Strategy),即什么情况下算用户认证通过了;Authorizer
:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能
访问应用中的那些功能;Realm
:可以有一个或者多个的realm,可以认为是安全实体数据源,即用于获取安全实体的,可
以用JDBC实现,也可以是内存实现等等,由用户提供;所以一般在应用中都需要实现自己的realmSessionManager
:管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用
在普通的JavaSE环境中CacheManager
:缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改
变,放到缓存中后可以提高访问的性能;Cryptography
:密码模块,Shiro 提供了一些常见的加密组件用于密码加密,解密等
二、授权方式
5.1、三种授权方式
5.2、授权流程
三、认证方式
Realm:域
Shiro
从 Realm
获取安全数据(如用户、角色、权限),就是说 SecurityManager
要验证用户身份,那么它需要从 Realm
获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm
得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm
看成 DataSource
String getName(); //返回一个唯一的Realm名字
boolean supports(AuthenticationToken token); //判断此Realm是否支持此Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException; //根据Token获取认证信息
自定义Realm
public class MyRealm implements Realm {
@Override
public String getName() {
return "myrealm";
}
@Override
public boolean supports(AuthenticationToken token) {
//仅支持UsernamePasswordToken类型的Token
return token instanceof UsernamePasswordToken;
}
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
if(!"zhang".equals(username)) {
throw new UnknownAccountException(); //如果用户名错误
}
if(!"123".equals(password)) {
throw new IncorrectCredentialsException(); //如果密码错误
}
//如果身份认证验证成功,返回一个AuthenticationInfo实现;
return new SimpleAuthenticationInfo(username, password, getName());
}
}
四、Shiro InI配置(通常直接使用ShiroConfig)
1、INI配置
[main]
#提供了对根对象securityManager及其依赖的配置
securityManager=org.apache.shiro.mgt.DefaultSecurityManager
…………
securityManager.realms=$jdbcRealm
[users]
#提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
username=password,role1,role2
[roles]
#提供了角色及权限之间关系的配置,角色=权限1,权限2
role1=permission1,permission2
[urls]
#用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器
/index.html = anon
/admin/** = authc, roles[admin], perms["permission1"]
2、InI配置中的四大类
[main]
部分
#声明一个realm
MyRealm1=com.shiro.mutilrealm.MyRealm1
MyRealm2=com.shiro.mutilrealm.MyRealm2
#配置验证器
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
# AllSuccessfulStrategy 表示 MyRealm1和MyRealm2 认证都通过才算通过
#配置策略
#authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
authcStrategy = com.shiro.authenticationstrategy.MyAuthenticationStrategy
#将验证器和策略关联起来
authenticator.authenticationStrategy = $authcStrategy
#配置验证器所使用的Realm
authenticator.realms=$MyRealm2,$MyRealm1
#把Authenticator设置给securityManager
securityManager.authenticator = $authenticator
[users]
部分
[users]
zhang=123,role1,role2
wang=123
[roles]
部分
[roles]
role1=user:create,user:update
role2=*
[main]
#告诉shiro我们用哪个加密算法
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
iniRealm.credentialsMatcher = $sha256Matcher
[users]
#用户名=密码,角色
admin=355b1bbfc96725cdce8f4a2708fda310a80e6d13315aec4e5eed2a75fe8032ce,role1
[urls]
部分
/login=anon
/unauthorized=anon
/static/**=anon
/authenticated=authc
/role=authc,roles[admin]
/permission=authc,perms["user:create"]
五、Shiro编码加密
1、散列算法
-
MD5加密
String str = “lyp”;
String salt = “123”;
String md5 = new Md5Hash(str, salt).toString();//还可以转换为 toBase64()/toHex()
-
SHA256算法
String str = “lyp”;
String salt = “123”;
String sha1 = new Sha256Hash(str, salt).toString(); -
SHA1算法
String str = “lyp”;
String salt = “123”;
//内部使用MessageDigest
String simpleHash = new SimpleHash(“SHA-1”, str, salt).toString(); -
SHA515算法
DefaultHashService hashService = new DefaultHashService(); //默认算法SHA-512
hashService.setHashAlgorithmName("SHA-512");
hashService.setPrivateSalt(new SimpleByteSource("123")); //私盐,默认无
hashService.setGeneratePublicSalt(true);//是否生成公盐,默认false
hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator());//用于生成公盐。默认就这个
hashService.setHashIterations(1); //生成Hash值的迭代次数
HashRequest request = new HashRequest.Builder()
.setAlgorithmName("MD5").setSource(ByteSource.Util.bytes("hello"))
.setSalt(ByteSource.Util.bytes("123")).setIterations(2).build();
String hex = hashService.computeHash(request).toHex();
SecureRandomNumberGenerator 用于生成一个随机数:
SecureRandomNumberGenerator randomNumberGenerator =
new SecureRandomNumberGenerator();
randomNumberGenerator.setSeed("123".getBytes());
String hex = randomNumberGenerator.nextBytes().toHex();
2、加密/解密
方式一
1、定义Realm
public class MyRealm extends AuthorizingRealm {
private PasswordService passwordService;
public void setPasswordService(PasswordService passwordService) {
this.passwordService = passwordService;
}
//省略doGetAuthorizationInfo,具体看代码
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return new SimpleAuthenticationInfo( "wu",passwordService.encryptPassword("123"),getName());
}
}
2、ini 配置(shiro-passwordservice.ini)
[main]
passwordService=org.apache.shiro.authc.credential.DefaultPasswordService
hashService=org.apache.shiro.crypto.hash.DefaultHashService
passwordService.hashService=$hashService
hashFormat=org.apache.shiro.crypto.hash.format.Shiro1CryptFormat
passwordService.hashFormat=$hashFormat
hashFormatFactory=org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory
passwordService.hashFormatFactory=$hashFormatFactory
passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher
passwordMatcher.passwordService=$passwordService
myRealm=com.github.zhangkaitao.shiro.chapter5.hash.realm.MyRealm
myRealm.passwordService=$passwordService
myRealm.credentialsMatcher=$passwordMatcher
securityManager.realms=$myRealm
方式二
1、生成密码散列值
String algorithmName = "md5";
String username = "lyp";
String password = "123";
String salt1 = username;
String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();
int hashIterations = 2;
SimpleHash hash = new SimpleHash(algorithmName, password, salt1 + salt2, hashIterations);
String encodedPassword = hash.toHex();
2、生成 Realm
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = "lyp"; //用户名及salt1
String password = "202cb962ac59075b964b07152d234b70"; //加密后的密码
String salt2 = "202cb962ac59075b964b07152d234b70";
SimpleAuthenticationInfo ai = new SimpleAuthenticationInfo(username, password, getName());
ai.setCredentialsSalt(ByteSource.Util.bytes(username+salt2)); //盐是用户名+随机数
return ai;
}
3、ini 配置
[main]
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
credentialsMatcher.storedCredentialsHexEncoded=true
myRealm=com.github.zhangkaitao.shiro.chapter5.hash.realm.MyRealm2
myRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$myRealm
此处最需要注意的就是 HashedCredentialsMatcher 的算法需要和生成密码时的算法一样。另外 HashedCredentialsMatcher 会自动根据 AuthenticationInfo 的类型是否是 SaltedAuthenticationInfo 来获取 credentialsSalt 盐。
3、加密登录处理 HashedCredentialsMatcher方式
-
自定义Realm
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
// 往数据库中查询用户
User user = userMapper.selectOne(new QueryWrapper().lambda().eq(User::getUsername, username));
if (user == null) {
// 账号不存在
throw new UnknownAccountException();
}
return new SimpleAuthenticationInfo(
user, //用户
user.getPassword(), //密码
ByteSource.Util.bytes(“lyp”), // 加密传入盐值
getName());//Realm name
} -
ShiroConifg
/**
- shiro配置类
*/
@Configuration
public class ShiroConfig {@Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager); // 登录配置 shiroFilter.setLoginUrl("/login"); shiroFilter.setSuccessUrl("/"); shiroFilter.setUnauthorizedUrl("/error/403"); // 自定义过滤器 Map<String, Filter> filtersMap = new LinkedHashMap<>(); filtersMap.put("mlfc", new MyLoginFilter()); shiroFilter.setFilters(filtersMap); // 拦截配置 Map<String, String> filterChainDefinitions = new LinkedHashMap<>(); filterChainDefinitions.put("/assets/**", "anon"); filterChainDefinitions.put("/login", "anon"); filterChainDefinitions.put("/reg", "anon"); filterChainDefinitions.put("/logout", "logout"); filterChainDefinitions.put("/**", "mlfc,user"); shiroFilter.setFilterChainDefinitionMap(filterChainDefinitions); return shiroFilter; } @Bean(name = "userRealm") @DependsOn("lifecycleBeanPostProcessor") public MyRealm userRealm() { MyRealm myRealm = new MyRealm(); myRealm.setCredentialsMatcher(credentialsMatcher()); // 自定义加密规则 return myRealm; } @Bean(name = "securityManager") public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm());//将Realm注入到SecurityManager中。 return securityManager; } //因为我们的密码是加过密的,所以,如果要Shiro验证用户身份的话,需要告诉它我们用的是md5加密的,并且是加密了两次。同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。 @Bean(name = "credentialsMatcher)" public HashedCredentialsMatcher credentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; }
}
-
登录接口
shiro 登录核心
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//执行登录方法,如果没有异常就说明OK了
subject.login(token);/** * 登录 */ @PostMapping("/login") @ResponseBody public R login(HttpServletRequest request, String username, String password, String code, boolean rememberMe) { if (StringUtil.isBlank(username, password)) { return R.failed("账号或密码不能为空"); } String sessionCode = (String) request.getSession().getAttribute("captcha"); if (code == null || !sessionCode.equals(code.trim().toLowerCase())) { return R.failed("验证码不正确"); } try { UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe); SecurityUtils.getSubject().login(token); return R.succeed("登录成功"); } catch (UnknownAccountException e) { return R.failed("用户不存在"); } catch (IncorrectCredentialsException e) { return R.failed("密码错误"); } catch (ExcessiveAttemptsException eae) { return R.failed("操作频繁,请稍后再试"); } }
-
shiro默认登录过期时间是30分钟
//永不过期,在登陆最开始加上
SecurityUtils.getSubject().getSession().setTimeout(-1000L);
//其他时间 单位毫秒
SecurityUtils.getSubject().getSession().setTimeout(1800000);
六、Shiro 的默认Filter
Filter名称
说明
anon
无参,匿名访问,无需认证就可访问
无参,匿名访问,无需认证就可访问
authc
org.apache.shiro.web.filter.authc.FormAuthenticationFilter
无参,表示需要认证才能访问
authcBasic
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
无参,表示需要httpBasic认证才能访问
logout
org.apache.shiro.web.filter.authc.LogoutFilter
退出拦截器,即退出后重定向的地址
noSessionCreation
org.apache.shiro.web.filter.session.NoSessionCreationFilter
不创建会话拦截器,调用 subject.getSession(false) 不会有什么问题,但是如果 subject.getSession(true) 将抛出 DisabledSessionException 异常;
perms
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
权限授权拦截器,验证用户是否拥有所有权限
port
org.apache.shiro.web.filter.authz.PortFilter
端口拦截器,端口号不是指定端口号,则跳转过去
rest
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
rest风格拦截器,根据请求方式来识别
roles
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
拥有某个角色权限才能访问
ssl
org.apache.shiro.web.filter.authz.SslFilter
无参,SSL拦截器,只有请求协议是https才能通过
user
org.apache.shiro.web.filter.authc.UserFilter
无参,用户已经身份验证,或记住我登录可以访问
示例:
/admin/**=anon
:无参,表示可匿名访问/admin/user/**=authc
:无参,表示需要认证才能访问/admin/user/**=authcBasic
:无参,表示需要httpBasic认证才能访问/admin/user/**=ssl
:无参,表示需要安全的URL请求,协议为https/home=user
:表示用户不一定需要通过认证,只要曾被 Shiro 记住过登录状态就可以正常发起 /home 请求/edit=authc,perms[admin:edit]
:表示用户必需已通过认证,并拥有 admin:edit 权限才可以正常发起 /edit 请求/admin=authc,roles[admin]
:表示用户必需已通过认证,并拥有 admin 角色才可以正常发起 /admin 请求/admin/user/**=port[8081]
:当请求的URL端口不是8081时,跳转到schemal://serverName:8081queryString/admin/user/**=rest[user]
:根据请求方式来识别,相当于/admins/user/**=perms[user:get]或perms[user:post]
等等/admin**=roles["admin,guest"]
:允许多个参数(逗号分隔),此时要全部通过才算通过,相当于hasAllRoles()/admin**=perms["user:add:*,user:del:*"]
:允许多个参数(逗号分隔),此时要全部通过才算通过,相当于isPermitedAll()
七、shiro注解权限控制-5个权限注解
Shiro共有5个注解
-
RequiresAuthentication:
-
RequiresGuest:
-
RequiresPermissions:
-
RequiresRoles:
-
RequiresUser
八 . Shiro - jsp标签
- 在页面上,如果要实现对某些文本、按钮等的控制,例如需要有什么角色或者权限才可以看见这个按钮,利用shiro自带的shiro标签能很容易就实现
1、引入标签
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro">
2、 shiro 标签
- shiro:authenticated (表示已认证通过,但不包括remember me登录的)
- shiro:guest (表示是游客身份,没有登录)
- shiro:hasAnyRoles(表示拥有这些角色中其中一个)
- shiro:hasPermission(表示拥有某一权限)
- shiro:hashRole (表示拥有某一角色)
- shiro:lacksPermission (表示不拥有某一角色)
- shiro:lacksRole (表示不拥有某一角色)
- shiro:notAuthenticated (表示没有通过验证)
- shiro:principal (表示用户的身份)
- shiro:user (表示已登录)
1.shiro:authenticated (表示已认证通过,但不包括remember me登录的)
<shiro:authenticated>
<label>用户身份验证已通过 </label>
</shiro:authenticated>
2.shiro:guest (表示是游客身份,没有登录)
<shiro:guest>
<label>您当前是游客,</label><a href="/login.jsp" >请登录</a>
</shiro:guest>
3.shiro:hasAnyRoles(表示拥有这些角色中其中一个)
<shiro:hasAnyRoles name="admin,user">
<label>这是拥有admin或者是user角色的用户</label>
</shiro:hasAnyRoles>
4.shiro:hasPermission(表示拥有某一权限)
<shiro:hasPermission name="admin:add">
<label>这个用户拥有admin:add的权限</label>
</shiro:hasPermission>
5.shiro:hashRole (表示拥有某一角色)
<shiro:hasRole name="admin">
<label>这个用户拥有的角色是admin</label>
</shiro:hasRole>
6.shiro:lacksPermission (表示不拥有某一权限)
<shiro:lacksPermission name="admin:delete">
<label>这个用户不拥有admin:delete的权限</label>
</shiro:lacksPermission>
7.shiro:lacksRole (表示不拥有某一角色)
<shiro:lacksRole name="admin">
<label>这个用户不拥有admin的角色</label>
</shiro:lacksRole>
8.shiro:notAuthenticated (表示没有通过验证)
<shiro:notAuthenticated>
<label>用户身份验证没有通过(包括通过记住我(remember me)登录的) </label>
</shiro:notAuthenticated>
9.shiro:principal (表示用户的身份)
-
取值取的是你登录的时候,在Realm 实现类中的new SimpleAuthenticationInfo(第一个参数,…) 放的第一个参数:
…
return new SimpleAuthenticationInfo(user,user.getPswd(), getName()); -
1)如果第一个放的是username或者是一个值 ,那么就可以直接用。
<shiro: principal/>
-
2)如果第一个参数放的是对象,比如放User 对象。那么如果要取其中某一个值,可以通过property属性来指定。
<shiro:principal property=“username”/>
10.shiro:user (表示已登录)
<shiro:user>
<label>欢迎[<shiro:principal/>],</label><a href="/logout.jsp">退出</a>
</shiro:user>
说明:只有已经登录(包含通过记住我(remember me)登录的)的用户才可以看到标签内的内容;一般和标签shiro:principal一起用,来做显示用户的名称
<shiro:lacksRole name="admin">
<shiro:lacksRole name="user">
<label>这个用户不拥有admin或user的角色</label>
</shiro:lacksRole>
</shiro:lacksRole>
九、示例Demo1(通过自定义token处理)
1、用户的角色与权限
用户
角色
权限
svip
svip
[查看、更新、删除、新增]
vip
vip
[查看、更新、新增]
user
p
[查看]
-
用户表
-
角色表
-
权限表
-
用户角色关系表
-
角色权限关系表
2、编写ShiroConfig
/**
* ShiroConfig配置
*/
@Configuration
public class ShiroConfig {
//SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
@Bean("securityManager")
public SecurityManager securityManager(AuthRealm authRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(authRealm); //将Realm注入到SecurityManager中。
return securityManager;
}
/**
* anno: 无需认证就可以访问
* authc: 必须认证才能访问
* user: 必须拥有 记住我 功能才能用
* perms: 拥有对某个资源的权限才能访问
* role: 拥有某个角色权限才能访问
*/
/**
* shiro如果使用ShiroConfig中shiroFiltet的map进行权限或角色拦截,会出现只走登陆认证,不走授权认证的情况。这是个巨坑!
//主要是这部分: 不要用这种方法,最好用注解的方法
filterMap.put("/add", "roles[admin]");
filterMap.put("/list", "roles[admin,user]");
filterMap.put("/delete", "perms[admin:delete]");
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
//auth过滤
Map<String, Filter> filters = new HashMap<>();
filters.put("auth", new AuthFilter());
shiroFilter.setFilters(filters);
Map<String, String> filterMap = new LinkedHashMap<>();
//authc表示需要验证身份才能访问,还有一些比如anon表示不需要验证身份就能访问等。
// anno匿名访问 auth验证
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/doc.html", "anon");
// 除了以上路径,其他都需要权限验证
filterMap.put("/**", "auth");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
//负责管理shiro的生命周期,项目中不加lifecyclebeanpostprocessor也没问题
@Bean("lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
3、自定义Realm
@Component
public class AuthRealm extends AuthorizingRealm {
@Autowired
private ShiroService shiroService;
private String salt = "lyp";
@Override
/**
* 授权 获取用户的角色和权限
*@param [principals]
*@return org.apache.shiro.authz.AuthorizationInfo
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1. 从 PrincipalCollection 中来获取登录用户的信息
User user = (User) principals.getPrimaryPrincipal();
//Integer userId = user.getUserId();
//2.添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
//2.1添加角色
simpleAuthorizationInfo.addRole(role.getRoleName());
for (Permission permission : role.getPermissions()) {
//2.1.1添加权限
simpleAuthorizationInfo.addStringPermission(permission.getPermission());
}
}
return simpleAuthorizationInfo;
}
@Override
/**
* 认证 判断token的有效性
*@param [token]
*@return org.apache.shiro.authc.AuthenticationInfo
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取token,既前端传入的token
String accessToken = (String) token.getPrincipal();
//1. 根据accessToken,查询用户信息
SysToken tokenEntity = shiroService.findByToken(accessToken);
//2. token失效
if (tokenEntity == null || tokenEntity.getExpireTime().isBefore(LocalDateTime.now())) {
throw new IncorrectCredentialsException("token失效,请重新登录");
}
//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
User user = shiroService.findByUserId(tokenEntity.getUserId());
//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
if (user == null) {
throw new UnknownAccountException("用户不存在!");
}
//5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
user, //用户
accessToken,
this.getName()); //Realm name
return info;
}
}
4、测试接口
@Api(tags = "测试")
@RestController
public class TestController {
@RequiresPermissions({"save"})
@PostMapping("/save")
public Map<String, Object> save(@RequestHeader("token")String token) {
System.out.println("save");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有save的权力");
return map;
}
@RequiresPermissions({"delete"})
@DeleteMapping("/delete")
public Map<String, Object> delete(@RequestHeader("token")String token) {
System.out.println("delete");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有delete的权力");
return map;
}
@RequiresPermissions({"update"})
@PutMapping("update")
public Map<String, Object> update(@RequestHeader("token")String token) {
System.out.println("update");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有update的权力");
return map;
}
@RequiresPermissions({"select"})
@GetMapping("select")
public Map<String, Object> select(@RequestHeader("token")String token) {
System.out.println("select");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有select的权力");
return map;
}
@RequiresRoles({"vip"})
@GetMapping("/vip")
public Map<String, Object> vip(@RequestHeader("token")String token) {
System.out.println("vip");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有VIP角色");
return map;
}
@RequiresRoles({"svip"})
@GetMapping("/svip")
public Map<String, Object> svip(@RequestHeader("token")String token) {
System.out.println("svip");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有SVIP角色");
return map;
}
@RequiresRoles({"p"})
@GetMapping("/p")
public Map<String, Object> p(@RequestHeader("token")String token) {
System.out.println("p");
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", 200);
map.put("msg", "当前用户有P角色");
return map;
}
}
5、登录处理
@Api(tags = "Shiro权限管理")
@RestController
public class ShiroController {
private final ShiroService shiroService;
public ShiroController(ShiroService shiroService) {
this.shiroService = shiroService;
}
/**
* 登录
*/
@ApiOperation(value = "登陆", notes = "参数:用户名 密码")
@PostMapping("/sys/login")
public Map<String, Object> login(@RequestBody @Validated LoginDTO loginDTO, BindingResult bindingResult) {
Map<String, Object> result = new HashMap<>();
if (bindingResult.hasErrors()) {
result.put("status", 400);
result.put("msg", bindingResult.getFieldError().getDefaultMessage());
return result;
}
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
//用户信息
User user = shiroService.findByUsername(username);
//账号不存在、密码错误
if (user == null || !user.getPassword().equals(password)) {
result.put("status", 400);
result.put("msg", "账号或密码有误");
} else {
//生成token,并保存到数据库
result = shiroService.createToken(user.getUserId());
result.put("status", 200);
result.put("msg", "登陆成功");
}
return result;
}
/**
* 退出
*/
@ApiOperation(value = "登出", notes = "参数:token")
@PostMapping("/sys/logout")
public Map<String, Object> logout(@RequestHeader("token")String token) {
Map<String, Object> result = new HashMap<>();
shiroService.logout(token);
result.put("status", 200);
result.put("msg", "您已安全退出系统");
return result;
}
}
通过自定义过滤器拦截,拒绝访问的请求通过executeLogin方法,校验是否能通过
**
* Shiro自定义auth过滤器
*
*/
@Component
public class AuthFilter extends AuthenticatingFilter {
// 定义jackson对象
private static final ObjectMapper MAPPER = new ObjectMapper();
/**
* 生成自定义token
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token
String token = TokenUtil.getRequestToken((HttpServletRequest) request);
return new AuthToken(token);
}
/**
* 步骤1.所有请求全部拒绝访问
*
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
return false;
}
/**
* 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法
*
* @param request
* @param response
* @return
* @throws Exception
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//获取请求token,如果token不存在,直接返回
String token = TokenUtil.getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
httpResponse.setCharacterEncoding("UTF-8");
Map<String, Object> result = new HashMap<>();
result.put("status", 403);
result.put("msg", "请先登录");
String json = MAPPER.writeValueAsString(result);
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
/**
* token失效时候调用
*/
@Override
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtil.getOrigin());
httpResponse.setCharacterEncoding("UTF-8");
try {
//处理登录失败的异常
Throwable throwable = e.getCause() == null ? e : e.getCause();
Map<String, Object> result = new HashMap<>();
result.put("status", 403);
result.put("msg", "登录凭证已失效,请重新登录");
String json = MAPPER.writeValueAsString(result);
httpResponse.getWriter().print(json);
} catch (IOException e1) {
}
return false;
}
}
查看源码,exeuteLogin内部使用的就是shiro的登录方法
自定义token类要继承UsernamePasswordToken
/**
* Shiro自定义token类
*
*/
public class AuthToken extends UsernamePasswordToken {
private String token;
public AuthToken(String token) {
this.token = token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
这里不继承 AuthenticationToken,因为getAuthenticationTokenClass()实际上获取到的是UsernamePasswordToken.class
十、示例Demo2 (shiro自动生成token,并实现记住我、md5加密和缓存)
1、用户的角色与权限
用户
角色
权限
admin
admin
[查看文件,拷贝文件、移动文件、重命名文件、下载文件、删除文件、上传文件、目录管理、创建目录、文件打分、评论文件、查看得分]
lyp
user
[查看文件,拷贝文件、下载文件、上传文件、目录管理、评论文件、查看得分]
2、编写ShiroConfig
/**
* shiro配置类
*
*/
@Configuration
public class ShiroConfig {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
// 登录配置
shiroFilter.setLoginUrl("/login");
shiroFilter.setSuccessUrl("/");
shiroFilter.setUnauthorizedUrl("/error/403");
// 自定义过滤器
Map<String, Filter> filtersMap = new LinkedHashMap<>();
filtersMap.put("mlfc", new MyLoginFilter());
shiroFilter.setFilters(filtersMap);
// 拦截配置
Map<String, String> filterChainDefinitions = new LinkedHashMap<>();
filterChainDefinitions.put("/assets/**", "anon");
filterChainDefinitions.put("/login", "anon");
filterChainDefinitions.put("/reg", "anon");
filterChainDefinitions.put("/logout", "logout");
filterChainDefinitions.put("/**", "mlfc,user");
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitions);
return shiroFilter;
}
@Bean(name = "userRealm")
@DependsOn("lifecycleBeanPostProcessor")//依赖于另一个组件,也就是说被依赖的组件会比该组件先注册到IOC容器中。
public MyRealm userRealm() {
MyRealm myRealm = new MyRealm();//自定义的Realm
myRealm.setCredentialsMatcher(credentialsMatcher()); // 将加密规则注入Realm
return myRealm;
}
//SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm());//将Realm注入到SecurityManager中。
securityManager.setCacheManager(cacheManager());//注入缓存对象。
securityManager.setRememberMeManager(rememberMeManager());//注入rememberMeManager()
return securityManager;
}
@Bean(name = "cacheManager")
public EhCacheManager cacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:shiro/ehcache-shiro.xml");
return cacheManager;
}
// 自定义加密规则
@Bean(name = "credentialsMatcher")
public HashedCredentialsMatcher credentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");
credentialsMatcher.setHashIterations(3);
return credentialsMatcher;
}
//负责管理shiro的生命周期,项目中不加lifecyclebeanpostprocessor也没问题
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
LifecycleBeanPostProcessor lifecycleBeanPostProcessor = new LifecycleBeanPostProcessor();
return lifecycleBeanPostProcessor;
}
/**
* shiro里实现的Advisor类,用来拦截注解的方法 .
*
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager());
return advisor;
}
/**
* 实现spring的自动代理
* 这个类将扫描上下文,寻找所有的Advistor(一个Advisor是一个切入点和一个通知的组成),将这些Advisor应用到所有符合切入点的Bean中,
* 即为匹配的目标Bean自动创建代理
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/**
* 配置ShiroDialect:用于thymeleaf和shiro标签配合使用
*/
@Bean("shiroDialect")
public ShiroDialect getShiroDialect() {
return new ShiroDialect();
}
//cookie对象
@Bean
public SimpleCookie rememberMeCookie() {
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
//cookie管理对象 处理记住我功能
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
cookieRememberMeManager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
return cookieRememberMeManager;
}
}
3、自定义Realm
/**
* 自定义认证授权处理逻辑
*
*/
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
@Autowired
private UserRoleMapper userRoleMapper;
@Autowired
private RoleAuthMapper roleAuthMapper;
/**
* 授权 获取用户的角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
User user = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
List<String> roleList = userRoleMapper.selectRoleCodesById(user.getId());
List<String> authList = roleAuthMapper.selectAuthCodesByRoleCodes(roleList);
Set<String> roleSet = new HashSet<>(roleList);
Set<String> authSet = new HashSet<>(authList);
authorizationInfo.setRoles(roleSet);
authorizationInfo.setStringPermissions(authSet);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
// 往数据库中查询用户
User user = userMapper.selectOne(new QueryWrapper<User>().lambda().eq(User::getUsername, username));
if (user == null) {
// 账号不存在
throw new UnknownAccountException();
}
// 密码验证shiro处理
return new SimpleAuthenticationInfo(
user, //用户
user.getPassword(), //密码
ByteSource.Util.bytes("lyp"), // 加密传入盐值
getName());//Realm name
);
}
}
4、测试接口
@RestController
public class ScoreController extends BaseController{
@Autowired
ScoreServiceImpl scoreService;
@Autowired
UserService userService;
/**
* 文件打分
* @param score
* @return
*/
@RequiresPermissions("file:score")
@PostMapping("/addScore")
public R addScore(Score score){
System.out.println("获取结果:"+score);
score.setUserId(getLoginUserId());
if(scoreService.addScore(score)){
return R.succeed("打分成功");
}
return R.failed("打分失败");
}
/**
* 获取文件得分
*/
@RequiresPermissions("file:check")
@GetMapping("/getScore/{fileId}")
public R getScore(@PathVariable Long fileId){
Score score = scoreService.queryScoreByFId(fileId);
return R.succeed(score,"查询成功");
}
}
5、登录处理
/**
* 登录
*/
@PostMapping("/login")
@ResponseBody
public R login(HttpServletRequest request, String username, String password, String code, boolean rememberMe) {
if (StringUtil.isBlank(username, password)) {
return R.failed("账号或密码不能为空");
}
String sessionCode = (String) request.getSession().getAttribute("captcha");
if (code == null || !sessionCode.equals(code.trim().toLowerCase())) {
return R.failed("验证码不正确");
}
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
try {
//执行登录方法,如果没有异常就说明OK了
subject.login(token);
return R.succeed("登录成功");
} catch (UnknownAccountException e) {
return R.failed("用户不存在");
} catch (IncorrectCredentialsException e) {
return R.failed("密码错误");
} catch (ExcessiveAttemptsException eae) {
return R.failed("操作频繁,请稍后再试");
}
}
6、前端权限控制
引入标签
<%@ taglib uri=“http://shiro.apache.org/tags” prefix=“shiro”>
<shiro:hasPermission="file:view" id="open"> 查看</a>