Java研学-Shiro安全框架(四)

六 SpringBoot集成Shiro认证

1 分析

  Shiro提供认证授权功能,所以SpringBoot中不需再编写自定义注解,权限拦截,登录拦截,登录登出。Shiro 环境中有三个封装对象Subject ,SecurityManager和Realms,SpringBoot 集成 Shiro 时需要配置相对应的Bean(Subject 不用)
shiro认证

2 导入依赖

<properties>
	<java.version>8</java.version>
	<shiro.version>1.7.1</shiro.version>
	<thymeleaf.extras.shiro.version>2.0.0</thymeleaf.extras.shiro.version>
</properties>
<!--Shiro核心框架 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- Shiro使用Spring框架 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- Shiro使用EhCache缓存框架 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!-- thymeleaf模板引擎和shiro框架的整合 -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>${thymeleaf.extras.shiro.version}</version>
</dependency>

3 创建数据源

// 有类才能生成Bean
public class EmployeeRealm extends AuthorizingRealm {
    @Autowired
    private IEmployeeService employeeService;
    @Autowired
    private IPermissionService permissionService;
    @Autowired
    private IRoleService roleService;
    //授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Employee currentEmployee= (Employee) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        if(currentEmployee.isAdmin()){
            List<Role> roles=roleService.listAll();
            for(Role role:roles){
                info.addRole(role.getSn());
            }
            info.addStringPermission("*:*");
        }else{
            List<Role> roleList=roleService.queryByEmployeeId(currentEmployee.getId());
            for(Role role:roleList){
                info.addRole(role.getSn());
            }
            //查询该用户的权限集合
            List<String> permissionList=permissionService.queryByEmployeeId(currentEmployee.getId());
            info.addStringPermissions(permissionList);
        }
        return info;
    }
     //认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 根据token获取用户名
        String username = (String) authenticationToken.getPrincipal();
        // 根据用户名查询用户
        Employee currentEmployee=employeeService.getByUsername(username);
        // 根据查询结果返回对应数据
        if(currentEmployee==null){
            return null;
        }
        return new SimpleAuthenticationInfo(currentEmployee,currentEmployee.getPassword()
                , ByteSource.Util.bytes(currentEmployee.getSalt()),getName());
    }
}

4 创建Shiro配置类

// 配置类注解
@Configuration
public class ShiroConfig {
    // 1.Realm 数据源从数据库中查询数据(先有Realm才能配置Bean,配置这个Bean需要先有这个类)
    // Bean一定是对象,对象不一定是Bean,对象需要基于类创建
    @Bean
    public EmployeeRealm employeeRealm(){
        EmployeeRealm realm = new EmployeeRealm();
        return realm;
    }
    // 2.SecurityManager 安全管理器(基于web环境下的)
    // 此处可用set调本类方法或传参的方式联系Realm    
    // 传参是在spring容器中查找这个Bean先类型再名字,去掉@Bean注解会报错(参数名与方法名尽量一致)
    // 调用方法首先不会运行该方法,会看方法的返回值类型,在容器中查找该类型,找到多个再按照名字去找,找到了就直接用
    // 不会运行该方法,若在容器中没找到该方法,就运行该方法并把返回值放到容器中,然后再拿过来用(无@Bean注解也行)
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(EmployeeRealm employeeRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(employeeRealm);
        return securityManager;
    }
    // 请求拦截器 shiro过滤器 由于创建麻烦此处使用工厂类创建过滤器对象
    // 若想知道一个工厂类返回什么类型的Bean 可查询其getObject()方法
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        // 配置登录页面
        shiroFilterFactoryBean.setLoginUrl("/static/login.html");
        // 配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 配置拦截规则(过滤链) 底层为双链表组成(有序,就是我们放入的顺序,过滤器根据顺序执行)
        LinkedHashMap<String,String> filterChainDefinitionMap=new LinkedHashMap<>();
        // 对静态资源设置匿名访问(浏览器图标 html css js)放行
        filterChainDefinitionMap.put("/favicon.ico**","anon");
        filterChainDefinitionMap.put("/static/**","anon");
        // 不需拦截的访问(公共资源)放行
        filterChainDefinitionMap.put("/login","anon");
        // 退出并且shiro清除session信息(上下文对象也就是用户信息) 执行退出方法
        // 无需在再编写退出方法,直接调用logout即可踢回登陆页面
        filterChainDefinitionMap.put("/logout","logout");
        // 进行拦截
        filterChainDefinitionMap.put("/**","authc");
        // 将拦截规则设置给拦截器链(shiro生成了很多拦截器,看我们选用哪个)
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        // 此时无登录信息 访问任何页面都应该被踢回登录页面
        return shiroFilterFactoryBean;
    }
}

5 LoginController – 登录方法

  登录认证实际上是shiro在做,但数据需要在realm中提供

@Controller
public class LoginController {
    @RequestMapping("/login")
    @ResponseBody
    public JsonResult login(String username, String password){
        // 他会自动从Spring容器中拿到SecurityManager设置给SecurityUtils
        // 然后再将SecurityManager设置给subject
        Subject subject = SecurityUtils.getSubject();
        // 将用户名密码封装到token
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        // 此处返回异常不精确到某一项,防止有人试错(先试帐号再试密码)
        try {
            // 登录失败就抛异常
            subject.login(token);
            subject.getSession().setAttribute("user_in_session",subject.getPrincipal());
        } catch (UnknownAccountException e) {
            return new JsonResult(false,"账号密码有误");
        } catch (IncorrectCredentialsException e) {
            return new JsonResult(false,"帐号密码有误");
        } catch (Exception e) {
            return new JsonResult(false,"系统异常,稍后再试");
        }
        return new JsonResult(true,"登录成功");
    }
}

6 数据源查询方法(service) – getByUsername

// IEmployeeService
Employee getByUsername(String username);
// EmployeeServiceImpl
public Employee getByUsername(String username) {
	return employeeMapper.getByUsername(username);
}

7 数据源查询方法(mapper) – getByUsername

// mapper
Employee getByUsername(String username);
// xml
<select id="getByUsername" resultMap="BaseResultMap">
      select e.id, e.username, e.name, e.password, e.email, e.age, e.admin,d.id d_id,d.name d_name,d.sn d_sn,e.salt
      from employee e left join department d on e.dept_id = d.id
      where username=#{username}
</select>

8 shiro 内置过滤器

  shiro 启动时会默认将以下这些类(过滤器)加载到程序中,然后使用Map将这些数据的关系以key value的方式存储起来。

过滤器的名称(key)Java 类(value)
anonorg.apache.shiro.web. lter.authc.AnonymousFilter
authcorg.apache.shiro.web. lter.authc.FormAuthenticationFilter
authcBasicorg.apache.shiro.web. lter.authc.BasicHttpAuthenticationFilter
rolesorg.apache.shiro.web. lter.authz.RolesAuthorizationFilter
permsorg.apache.shiro.web. lter.authz.PermissionsAuthorizationFilter
userorg.apache.shiro.web. lter.authc.UserFilter
logoutorg.apache.shiro.web. lter.authc.LogoutFilter
portorg.apache.shiro.web. lter.authz.PortFilter
restorg.apache.shiro.web. lter.authz.HttpMethodPermissionFilter
sslorg.apache.shiro.web. lter.authz.SslFilter

anon: 匿名拦截器,即不需要登录即可访问(谁都可以访问,不需要拦截);一般用于静态资源过滤;示例“/static/**=anon”

authc: 表示需要认证(登录)才能使用,该路径所有请求都需登录后才能访问;示例“/**=authc”

authcBasic:Basic HTTP身份验证拦截器

roles: 角色授权拦截器,验证用户是否拥有资源角色;示例“/admin/**=roles[admin]”

perms: 权限授权拦截器,验证用户是否拥有资源权限;示例“/user/create=perms[“user:create”]”

user: 用户拦截器,用户已经身份验证/记住我登录的都可;示例“/index=user”

logout: 退出拦截器,登出后自动清理session中的用户信息。主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout”

port: 端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样

rest: rest风格拦截器;

ssl: SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;

9 400错误问题解决

  当传入的参数 SpringMVC 无法转换时,就会出现400问题(第一次访问时出现),session是在浏览器第一次访问服务器时,由服务器创建并生成一个sessionID,通过response响应给浏览器。

  浏览器访问服务器时,服务器中有一个session池,当找到session后,将返回对应的JsessionID。

  但第一次访问时会首先经过shiro过滤器,其中有一个会话管理器(SessionManager),他发现当前是第一次访问,因此会进行一次URL重写,服务器会生成session并把sessionID返回给安全管理器,会话管理器通过重定向回到浏览器,再次发起申请访问服务器,此时第一次访问服务器实际上是没有访问到服务器,因此服务器无法接收携带的参数(JsessionID)报400错误。(去掉url中的JsessionID即可访问)

  第二次访问时,session已经存在,通过id寻找session,可以正常访问。或者告诉会话管理器,不需要做url重写,可在shiroconfig中生成会话管理器,将 setSessionIdUrlRewritingEnabled 设置为false即可(默认为true)。

@Configuration
public class ShiroConfig {
    // 略
    // 会话管理器
    @Bean
    public DefaultWebSecurityManager sessionManager(){
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        // url重写开关
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
}

  此处需注意,编写好 sessionManager 会话管理器后,需要将其设置给 SecurityManager 安全管理器(通过参数)

@Configuration
public class ShiroConfig {
	// 略
    @Bean
    //安全管理器
    public DefaultWebSecurityManager defaultWebSecurityManager(EmployeeRealm employeeRealm, DefaultWebSessionManager sessionManager){
        // 创建安全管理器
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        // 设置realm
        securityManager.setRealm(employeeRealm);
        // 设置会话管理器
        securityManager.setSessionManager(sessionManager);
        return securityManager;
    }
    //会话管理器
    @Bean
    public DefaultWebSecurityManager sessionManager(){
        DefaultWebSessionManager sessionManager=new DefaultWebSessionManager();
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泰勒疯狂展开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值