java 安全框架 shiro_Spring Boot上的Shiro安全框架

Shiro权限控制之登录认证

前言:相信点进来的同学大部分是刚接触shiro框架

所以我们从最基础开始,当然我会抛开那些shiro的官方图(真的有人会认真看那玩意儿?),一步步向大家讲解shiro的配置过程及登录认证的简单实现

Shiro是用来帮助我们做权限管理的,本篇文章的shiro使用在Web项目上,所以我用了最新的spring boot为框架。(当然使用xml来进行配置也可以,原理是一样的,只是写法不同)

在开始学习之前理解什么是shiro的权限管理?

我们知道shiro的主要功能有认证,授权,加密,会话管理,缓存等

一大堆功能会让你觉得学起来毫无胃口,这里我们主要知道什么是认证,授权就行

(这样理解肯定不准确,但是更易懂)

认证就是登录认证:你登录了这个网页,shiro会通过一个口令(这里我们用token)来认证你,当然你也会用这个口令去得到服务器的认可,进行后续的权限操作;

授权就是权限受理:shiro会根据你提供的信息进行认证之后,给予你相应的权力(如删除,添加等);

要记住Shiro不会给你创建和维护关系表,需要我们自己在数据库创建出对应的关系表:用户——角色——权限

让我们看下这几张表:

1.user(用户表)

73fdda837ec0f3af378b737b9bd35412.png

2.role(角色表)

9321d1d9b6bf009204ed76ce5def6cb2.png

3.permission(权限表)

d3b2263abbbfc35cad10c6d025fcedce.png

用户和角色是一对多的关系,一个用户可以拥有多个角色(比如管理员,普通用户)

角色和权限是多对多的关系,一个角色可以用个多个权限,一个权限也能对应多个用户

当然还有关联表,这里不多说,因为我们只做登录验证,所以目前只需要一张用户表即可

那么什么是登录认证,我想很多初学者会曲解它的意思,它并不是帮助你去登录用户名账号的。

要真正理解它,我们就需要知道shiro是用来干什么的?登录认证在shiro中起什么作用?

前面说了shiro是用来做权限管理的,而登录之后怎样才能让shiro一直记得你,这就是登录认证的作用

那么有同学就会问,为什么要用shiro的认证,而不去使用数据库的用户表来认证?

这个问题我也问过,继续理解便会知道:

因为你之后的每次操作都要用服务端返回给你的数据来校验,如果使用User表数据是极不安全和不可靠的,既然加入了shiro框架,就要考虑到安全性,所以我们会使用token来进行校验,这也是本篇文章的重点!

废话不多说,我们开始吧:

第一步:引入相关包

这里我使用maven来进行包的管理:

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

cn.lxt

demo

0.0.1-SNAPSHOT

jar

demo

Demo project for Spring Boot

org.springframework.boot

spring-boot-starter-parent

1.5.8.RELEASE

UTF-8

UTF-8

1.8

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter-thymeleaf

1.5.8.RELEASE

org.springframework.boot

spring-boot-starter-jdbc

1.5.8.RELEASE

org.springframework.boot

spring-boot-starter-data-jpa

1.5.8.RELEASE

org.springframework.boot

spring-boot-starter-data-rest

1.5.8.RELEASE

org.springframework.boot

spring-boot-devtools

1.5.8.RELEASE

true

true

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.3.1

mysql

mysql-connector-java

5.1.38

org.mybatis

mybatis

3.4.5

org.mybatis.generator

mybatis-generator-core

1.3.5

org.springframework.boot

spring-boot-starter-aop

1.5.8.RELEASE

junit

junit

4.12

com.alibaba

druid

1.1.5

org.apache.shiro

shiro-spring

1.3.2

org.apache.shiro

shiro-ehcache

1.3.2

org.apache.shiro

shiro-cas

1.3.2

org.springframework.boot

spring-boot-maven-plugin

true

org.mybatis.generator

mybatis-generator-maven-plugin

1.3.5

true

true

mysql

mysql-connector-java

5.1.30

第二步:配置Shiro

pom配置好之后,我们就要用java编写shiro的全局配置类。

在配置shiro之前我们需要明白它的三大要素:

Subject:单个对象,与如何应用交互的用户对象;

SecurityManager:安全管理器,管理Subject;

Realm:域,SecurityManager与Realm交互获得数据(用户-角色-权限)

知道这些后我们开始新建一个ShiroConfig类:

(因为本篇只学习登录认证,所以我们先不用缓存管理,密码编码等功能)

package cn.lxt.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;

import org.apache.shiro.cache.ehcache.EhCacheManager;

import org.apache.shiro.mgt.SecurityManager;

import org.apache.shiro.spring.LifecycleBeanPostProcessor;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.DependsOn;

import java.util.LinkedHashMap;

import java.util.Map;

@Configuration

public class shiroConfig {

/**

* 负责shiroBean的生命周期

*/

@Bean

public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){

return new LifecycleBeanPostProcessor();

}

/**

*这是个自定义的认证类,继承子AuthorizingRealm,负责用户的认证和权限处理

*/

@Bean

@DependsOn("lifecycleBeanPostProcessor")

public MyShiroRealm shiroRealm(){

MyShiroRealm realm = new MyShiroRealm();

//realm.setCredentialsMatcher(hashedCredentialsMatcher());

return realm;

}

/** 安全管理器

* 将realm加入securityManager

* @return

*/

@Bean

public SecurityManager securityManager(){

//注意是DefaultWebSecurityManager!!!

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

securityManager.setRealm(shiroRealm());

return securityManager;

}

/** shiro filter 工厂类

* 1.定义ShiroFilterFactoryBean

* 2.设置SecurityManager

* 3.配置拦截器

* 4.返回定义ShiroFilterFactoryBean

*/

@Bean

public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){

//1

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//2

//注册securityManager

shiroFilterFactoryBean.setSecurityManager(securityManager);

System.out.println("11");

//3

// 拦截器+配置登录和登录成功之后的url

//LinkHashMap是有序的,shiro会根据添加的顺序进行拦截

Map filterChainDefinitionMap = new LinkedHashMap();

//配置不会被拦截的连接 这里顺序判断

//anon,所有的url都可以匿名访问

//authc:所有url都必须认证通过才可以访问

//user,配置记住我或者认证通过才能访问

//logout,退出登录

filterChainDefinitionMap.put("/JQuery/**","anon");

filterChainDefinitionMap.put("/js/**","anon");

//配置退出过滤器

filterChainDefinitionMap.put("/example1","anon");

filterChainDefinitionMap.put("/lxt","anon");

filterChainDefinitionMap.put("/login","authc");

filterChainDefinitionMap.put("/success","anon");

filterChainDefinitionMap.put("/index","anon");

filterChainDefinitionMap.put("/Register","anon");

filterChainDefinitionMap.put("/logout","logout");

//过滤连接自定义,从上往下顺序执行,所以用LinkHashMap /**放在最下边

filterChainDefinitionMap.put("/**","authc");

//设置登录界面,如果不设置为寻找web根目录下的文件

shiroFilterFactoryBean.setLoginUrl("/lxt");

//设置登录成功后要跳转的连接

shiroFilterFactoryBean.setSuccessUrl("/success");

//设置登录未成功,也可以说无权限界面

shiroFilterFactoryBean.setUnauthorizedUrl("/403");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

System.out.println("shiro拦截工厂注入类成功");

//4

//返回

return shiroFilterFactoryBean;

}

}

以上需要注意几点:

1.shiroFilter是入口,主要有四步操作,代码中已经注释清楚

2.shiroFilterFactoryBean.setLoginUrl("/lxt");启动类不管你输入怎样的url,他都会跳转到登录启动类;

3.shiroFilterFactoryBean.setSuccessUrl("/success");登录成功后跳转的类,这个方法大家可以不用管,因为我感觉它根本用不到,大神别喷!

第三步:配置Realm

看完了ShiroConfig类之后,许多人会问:噫!我的MyShiroRealm怎么导入不进来!

其实这个方法的调用需要我们自己再写一个Realm类继承AuthorizingRealm。

继承之后我们需要重写两个方法:

1.doGetAuthorizationInfo()方法用于角色和权限的控制,暂不使用;

2.doGetAuthenticationInfo()方法用于登录认证,重点。

下面贴出代码:

package cn.lxt.shiro;

import cn.lxt.bean.User;

import cn.lxt.service.UsersService;

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.springframework.beans.factory.annotation.Autowired;

public class MyShiroRealm extends AuthorizingRealm {

@Autowired

private UsersService usersService;

/**

* 用于获取登录成功后的角色、权限等信息

* @param principalCollection

* @return

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

return null;

}

/**

* 验证当前登录的Subject

* @param token

* @return

* @throws AuthenticationException

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

//拿到账号(username)

String username = (String) token.getPrincipal();

System.out.println("username=:"+username);

//检查token的信息

System.out.println(token.getCredentials());

User user = usersService.findByName(username);

if (user==null){

return null;

}

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),getName());

return info;

}

}

通过以上代码你会发现,我们是怎样进行验证的,进行验证的关系点是传入的参数token

现在大家应该明白了token在本篇文章中的作用!

当然有些同学看到这里还是云里雾里,在这我稍微讲解一些思路:

1.当我们进行账号密码登录的时候,会创建一个token(token只是一种概念,具体的实现还是要定义的)到数据库;

2.token存入的时候绑定了登录传入的用户名和密码(token又很多实现类,推荐使用UsernamePasswordToken);

3.shiro自带的框架会将token与SimpleAuthenticationInfo类对象进行比较,失败抛出指定异常(需要自己捕获)

第四步:Controller的编写

完成上面shiroFactory和realm的配置之后;

我们就要真正的去调用shiro的认证功能了

要明白,在shiro的登录认证中:

Controller帮你获取post参数后,

进行参数绑定,再调用subject.login()方法;

如果用户名密码正确,会跳转SuccessUrl,

所以说Controller获取参数后注入给Shiro,信息错误则在Controller中报错

@PostMapping(value = "testLogin")

public Map testLogin(@RequestParam("name")String name,@RequestParam("password")String password){

Map map = new HashMap();

//创建subject实例

Subject subject = SecurityUtils.getSubject();

//判断当前的subject是否登录

if (subject.isAuthenticated()==false){

//将用户名和密码存入UsernamePasswordToken中

UsernamePasswordToken token = new UsernamePasswordToken(name,password);

try {

//将存有用户名和密码的token存进subject中

subject.login(token);

}catch (UnknownAccountException uae){

System.out.println("没有用户名为"+token.getPrincipal()+"的用户");

} catch (IncorrectCredentialsException ice){

System.out.println("用户名为:"+token.getPrincipal()+"的用户密码不正确");

} catch (LockedAccountException lae){

System.out.println("用户名为:"+token.getPrincipal()+"的用户已被冻结");

} catch (AuthenticationException e){

System.out.println("未知错误!");

}

}

return "success";

}

第五步:在Restful风格下的实现

以上只是在springmvc中的shiro实现,

但是实际开发中,前后端分离越来越流行,

分离之后的RestFulApi我们要怎么实现shiro呢?

在这里我的想法是自己创建token

RestFul下的思路:

1.当我们进行账号密码登录的时候,会创建一个token(UUID随机生成)

2.token存入的时候要记得它是随机生成的,生成之后会与用户登录的id进行绑定;

3.我们登录完成之后,返回给浏览器的JSON对象要包含token值,浏览器会把token值存入到浏览器中。

思路清楚之后我们要进行实现:

1.创建token:

package cn.lxt.controller;

import cn.lxt.bean.User;

import cn.lxt.service.TokenService;

import cn.lxt.service.UsersService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.PostMapping;

import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

import java.util.Map;

@Controller

public class LoginController {

@Autowired

private UserService userService;

@Autowired

private TokenService tokenService;

@ApiOperation(value = "登录验证",notes = "成功返回200,失败返回500,返回一个TokenJSON对象")

@ApiImplicitParams({

@ApiImplicitParam(name = "name",value = "账号名",required = true,dataType = "String"),

@ApiImplicitParam(name = "password",value = "密码",required = true,dataType = "String")

})

@RequestMapping(value = "/ajaxLogin",method = RequestMethod.POST)

public Map ajaxLogin(@RequestParam("name")String name, @RequestParam("password")String password){

tokenService.checkExpire();

Map map = new HashMap();

User user = new User(name,password);

int status = userService.queryUser(user);

if (status==200){

map = tokenService.createToken(user);

}

map.put("status",status);

return map;

}

}

在controller中返回一个User和Token给前端;

2.在Service中创建token,并且存入数据库:

package cn.lxt.service.Impl;

import cn.lxt.bean.Token;

import cn.lxt.dao.TokenMapper;

import cn.lxt.service.TokenService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.Date;

import java.util.HashMap;

import java.util.Map;

import java.util.UUID;

@Service

public class TokenServiceImple implements TokenService{

private static final int Expire = 3600*25;

@Autowired

private UserMapper userMapper;

@Autowired

private TokenMapper tokenMapper;

@Override

public Map createToken(User user) {

User user1 = userMapper.selectByNameAndPassword(user);

//创建TokenEntity参数

String newtoken = UUID.randomUUID().toString();

Date updateTime = new Date();

Date expireTime = new Date(updateTime.getTime()+Expire*1000);

Token token = new Token(newtoken,user1.getId(),updateTime,expireTime);

//判断token是否已经存在,不存在就存入,存在就更新

if (tokenMapper.findByUserId(user1.getId())==null){

tokenMapper.insert(token);

System.out.println("存入成功");

}else {

tokenMapper.updateByToken(token);

System.out.println("更新成功");

}

Map map = new HashMap();

map.put("token",token);

return map;

}

@Override

public void checkExpire() {

Date now = new Date();

List list = tokenMapper.selectByExample(new TokenExample());

for (Token token:list){

if (token.getExpiretime().getTime()

tokenMapper.deleteByExpireTime(token);

System.out.println(token.getTokenid()+"已删除");

}

}

}

}

上面创建token的时候因为时间原因没有判断用户Id的token是否已在数据库存在,你们可以自己试下;

3.OK,我们token已经创建了,并且把它以JSON的格式穿了过去,现在要做的就是把token存到浏览器中:

在登录界面的登录按钮上,我们设置一个js方法:

function login() {

var name = document.getElementById('name').value;

console.log(name);

var password=document.getElementById('password').value;;

var url='http://localhost:8088/ajaxLogin'

$.ajax({

url:url,

type:'post',

data:{name:name,password:password},

datatype:'json',

success:function (result) {

if(result.status==200){

localStorage.setItem("token",result.token)

console.log(result)

}else if(result.status=500){

alert('登录失败!')

}

}

})

}

上面代码把token传进localStorage中了。

732fd82ce65920642796b8fe8848ad45.png

但是,细心的同学会发现,虽然存进了localStorage中,但是从请求头传给后端是最优解决方案,也就是需要将token附加在Header里,而且我们要做到访问任意url,都能把token从localStorage转存到Header中,这个问题就交给机智的你们了,如果实在做不出来可以私信我

以上便是Spring Boot上Shiro安全框架的登录验证简单实现;

觉得还可以请点个赞,赞不了也可以收藏;

总之,谢谢阅读~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值