您的位置:首页 > 其它

Shiro2.整合登陆认证和权限控制

2018-01-01 23:47 661 查看

前言

上一篇简单了解Shiro框架的几个比较重要的类,清楚在使用Shiro应该配置哪些。本篇则从实际角度出发开发整合登陆认证和权限控制案例。

本实例环境:IDEA + Maven

本实例采用的主要技术:SpringBoot +Shiro+Thymeleaf

本次学习目标

登陆之后才有权访问控制链接,否则跳转到登录页面;

对Url链接进行权限控制,仅当前用户有该链接的访问权限才能访问,否则跳转到指定页面403 forbidden页面;

登陆过程中用户名密码错误或账号锁定情况时返回相应相应message。

pom引入Shiro依赖

<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>

<!--Shiro辅助包配置RedisManager-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>


Shiro配置

@Configuration
@ConfigurationProperties(prefix = "spring.redis")
public class ShiroConfig {

/**
* 获取资源接口;
*/
@Autowired(required = false)
private ResourcesService resourcesService;

private String host;

private int port;

private int timeout;

/**
* shiro生命周期;
* @return
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

/**
* ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
Filter Chain定义说明
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();

// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/usersPage");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/img/**","anon");
filterChainDefinitionMap.put("/font-awesome/**","anon");
//<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//自定义加载权限资源关系
List<Resources> resourcesList = resourcesService.queryAll();
for(Resources resources:resourcesList){

if (StringUtil.isNotEmpty(resources.getResurl())) {
String permission = "perms[" + resources.getResurl()+ "]";
filterChainDefinitionMap.put(resources.getResurl(),permission);
}
}
filterChainDefinitionMap.put("/**", "authc");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}

@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
//securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}

@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}

/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
*  所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();

hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));

return hashedCredentialsMatcher;
}

/**
*  开启shiro aop注解支持.
*  使用代理方式;所以需要开启代码支持;
* @param
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}

/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(timeout);
// redisManager.setPassword(password);
return redisManager;
}

/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}

/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}

/**
* shiro session的管理
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}


ShiroConfig
配置类首先从
securityManager
方法看,在该方法中需要设置Realm,SessionManager,CacheManager等等,因为SecurityManager是Shiro的主入口,典型的Facade模式,几乎所有相关的权限操作,都由他代理了。

其次在
shirFilter
方法中通过ShiroFilterFactoryBean创建shiroFilter,Shiro默认fitler配置有这些:

public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
···
}


其权限过滤器及配置释义:

权限写法常见用法备注
anon/admins/**=anon没有参数,表示可以匿名使用
authc/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
roles/admins/user/**=roles[“admin,guest”]每个参数通过才算通过,相当于hasAllRoles()方法。
perms/admins/user/*=perms[“user:add:,user:modify:*”]当有多个参数时必须每个参数都通过才通过,想当isPermitedAll()方法
rest:/admins/user/**=rest[user]根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
authcBasic/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查
port/admins/user/**=port[8081]当请求的url的端口不是8081则跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数
controller登陆

在Controller层接口接受VO请求数据, 在认证过程中,用户需要提交实体信息(
Principals
)和凭据信息(
Credentials
)以检验用户是否合法。
UsernamePasswordToken
支持最常见的用户名/密码的认证机制,通过
subject.login(token)
提交认证操作。

/**
* POST提交数据;
*
* @param request requst请求;
* @param user    user对象数据;
* @param model
* @return
*/
@PostMapping(value="/login")
public String login(HttpServletRequest request, User user, Model model){
if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword())) {
request.setAttribute("msg", "用户名或密码不能为空!");
return "login";
}
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());
try {
subject.login(token);
return "redirect:usersPage";
}catch (LockedAccountException lae) {
token.clear();
request.setAttribute("msg", "用户已经被锁定不能登录,请与管理员联系!");
return "login";
} catch (AuthenticationException e) {
token.clear();
request.setAttribute("msg", "用户或密码不正确!");
return "login";
}
}


目前还没做前后端分离,所以返回时通过页面跳转来实现。认证通过后跳转至usersPage请求对应的页面,错误则跳转至login页面。

Realm认证,授权

第一篇提到过,在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的,通过覆写
doGetAuthenticationInfo
完成认证操作,

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
User user = userService.selectByUsername(username);
if(user==null){
throw new UnknownAccountException();
}
if (0==user.getEnable()) {
throw new LockedAccountException(); // 帐号锁定
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, //用户
user.getPassword(), //密码
ByteSource.Util.bytes(username),
getName()  //realm name
);
// 当验证都通过后,把用户信息放在session里
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", user);
session.setAttribute("userSessionId", user.getId());
return authenticationInfo;
}


该方法主要执行以下操作:

1. 通过
token.getPrincipal()
获取令牌信息,该数据对应Controller层
UsernamePasswordToken(user.getUsername(),user.getPassword());
中的
user.getUsername()
;

2. 根据令牌信息从数据源(通常为数据库)中获取用户对象 ;

3. 针对返回结果进行验证;

4. 验证通过将返回一个封装了用户信息的
AuthenticationInfo
实例;

5. 验证失败则抛出
AuthenticationException
异常信息。

shiroFilter拦截配置

/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是报错的,因为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
Filter Chain定义说明
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();

// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/usersPage");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
//拦截器.
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();

//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//自定义加载权限资源关系
List<Resources> resourcesList = resourcesService.queryAll();
for(Resources resources:resourcesList){

if (StringUtil.isNotEmpty(resources.getResurl())) {
String permission = "perms[" + resources.getResurl()+ "]";
filterChainDefinitionMap.put(resources.getResurl(),permission);
}
}
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}


到此关键配置已完成,接下来运行程序进行验证。

项目下载地址:springboot-shiro-2 整合登陆认证和权限控制
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: