Springboot 整合shiro实现权限控制的方法

网友投稿 268 2022-11-15


Springboot 整合shiro实现权限控制的方法

Author:jeffrey

Date:2019-04-08

一、开发环境:

1、mysql - 5.7

2、navicat(mysql客户端管理工具)

3、idea 2017.2

4、jdk8

5、tomcat 8.5

6、springboot2.1.3

7、mybatis 3

8、shiro1.4

9、maven3.3.9

二、数据库设计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CB46ByC1-1604249108144)(img/shiro01.png)]

三、创建springboot项目

3.1 添加组件

添加 web、lombok、thymeleaf、jdbc、mysql、mybatis等模块;

3.2 pom.xml

org.springframework.boot

spring-boot-starter-parent

2.1.4.RELEASE

org.springframework.boot

spring-boot-starter-jdbc

org.springframework.boot

spring-boot-starter-thymeleaf

org.springframework.boot

spring-boot-starter-web

org.mybatis.spring.boot

mybatis-spring-boot-starter

2.0.1

org.apache.shiro

shiro-spring

1.4.0

com.alibaba

druid

1.0.26

mysql

mysql-connector-java

5.1.32

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-maven-plugin

3.3 创建项目包结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNuns5Pt-1604249108145)(img/shiro02.png)]

3.4 配置初始化文件application.yml

#配置服务端口号

server:

port: 8080

#配置数据源

spring:

datasource:

type: com.alibaba.druid.pool.DruidDataSource

driver-class-name: com.mysql.jdbc.Driver

url: jdbc:mysql://localhost:3306/my_shiro?useUnicode=true&characterEncoding=utf-8

username: root

password: 59852369

#配置mybatis

mybatis:

mapper-locations: classpath:mapper/*.xml

type-aliases-package: com.qf.domain.pojo

四、程序设计开发

4.1 实体类开发

​ SysUser.java

package com.qf.domain;

import lombok.Data;

import java.io.Serializable;

import java.util.Date;

/**

* Created by 54110 on 2019-07-05.

*/

@Data

public class SysUser implements Serializable {

private int userId;//用户id

private String loginName;//登录名

private String password;//

private Integer state;

private Date createTime;

private String realname;

}

SysPermission.java

packaggKCjxe com.qf.domain;

import lombok.Data;

import java.io.Serializable;

/**

* Created by 54110 on 2019-07-05.

*/

@Data

public class SysPermission implements Serializable {

private int permId;

private String permName;//权限名称

private String permUrl;//权限操作地址(路径)

private String menuName;//菜单名

private String menugKCjxLevel;//菜单级别(11:一级;12:二级。。。)

private String menuCode;//菜单编码(每级两位数字)

private int ifValid;

private String parentCode;

}

4.2 数据访问层接口开发

SysUserMapper.java

package com.qf.mapper;

import com.qf.domain.SysUser;

import org.apache.ibatis.annotations.Mapper;

/**

* Created by 54110 on 2019-07-05.

*/

@Mapper

public interface SysUserMapper {

public SysUser findUserByUsername(String username);

}

SysPermissionMapper

package com.qf.mapper;

import com.qf.domain.SysPermission;

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**

* Created by 54110 on 2019-07-05.

*/

@Mapper

public interface SysPermissionMapper {

// 根据用户登录名查询其所拥有的权限

public List findPermissionsByLoginName(String loginName);

}

4.3 Mybatis映射开发

SysUsersMapper.xml

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

PASSWORD,LOGIN_NAME,CREATE_TIME,REALNAME,STATE

SELECT

FROM

TB_SYS_USER US

WHERE

US.LOGIN_NAME = #{name}

SysPermissionMapper.xml

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

SELECT

p.*

FROM

TB_SYS_USER us ,

TB_USER_ROLE ur,

TB_SYS_ROLE r,

TB_ROLE_PERMISSION rp,

TB_SYS_PERMISSION p

WHERE

us.USERID = ur.USER_ID AND ur.ROLE_ID = r.ROLE_ID

AND r.ROLE_ID = rp.ROLE_ID AND rp.PERMISSION_ID = p.PERMISSION_ID

AND trim(us.LOGIN_NAME) = #{loginName}

ORDER BY p.MENU_CODE

4.4 业务层开发

SysUsersServiceImpl.java

package com.qf.service.impl;

import com.qf.domain.SysUser;

import com.qf.mapper.SysUserMapper;

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

import org.springframework.stereotype.Service;

/**

* Created by 54110 on 2019-07-05.

*/

@Service

public class SysUsersServiceImpl {

@Autowired

private SysUserMapper userMapper;

public SysUser queryUserByLoginName(String loginName) {

SysUser tbUsers = userMapper.findUserByUsername(loginName);

return tbUsers;

}

}

​SysPermissionServiceImpl.java

package com.qf.service.impl;

import com.qf.domain.SysPermission;

import com.qf.mapper.SysPermissionMapper;

import com.qf.service.SysPermissionService;

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

import org.springframework.stereotype.Service;

import java.util.List;

/**

* Created by 54110 on 2019-07-05.

*/

@Service

public class SysPermissionServiceImpl implements SysPermissionService {

@Autowired

private SysPermissionMapper permMapper;

@Override

public List queryPermissionsByLoginName(String loginName) {

List list = permMapper.findPermissionsByLoginName(loginName);

return list;

}

}

4.5 控制层接口开发

UserController.java

package com.qf.controller;

import com.qf.service.SysUserService;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.UsernamePasswordToken;

import org.apache.shiro.subjegKCjxct.Subject;

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

import org.springframework.stereotype.Controller;

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

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

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

import java.util.Map;

/**

* Created by 54110 on 2019-07-05.

*/

@Controller

public class UserController {

@Autowired

private SysUserService userService;

// 登录页(view)展示

@RequestMapping("/login")

public String showlogin(){

return "login";

}

/**

* 登录处理

* @param map 用户登录表单数据

* @return 逻辑视图

*/

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

public String dealLogin(@RequestParam Map map){

System.out.println( map.values().toString());

try {

Subject subject = SecurityUtils.getSubject();//从安全管理器中获取主体对象

UsernamePasswordToken token = new UsernamePasswordToken();//构建令牌对象

token.setUsername(map.get("name").toString());//赋身份信息

token.setPassword(map.get("password").toString().toCharArray());//赋凭证信息

subject.login(token);//使用主体的login方法判定用户的权限

if(subject.isAuthenticated()){

// 已登陆

// 用户信息及权限信息的存储(session|| redis)

return "main";

}

} catch (AuthenticationException e) {

e.printStackTrace();

System.out.println("登录失败");

}

return "login";

}

// 登录且拥有user:

@RequestMapping("/one")

public String showCaseOne(){

return "one";

}

@RequestMapping("/two")

public String showCaseTwo(){

return "two";

}

// 权限不足时,响应的页面

@RequestMapping("/unauth")

public String showPermission(){

return "unauth";

}

// 用户注销操作

@RequestMapping("/logout")

public String logout(){

Subject subject = SecurityUtils.getSubject();

subject.logout();//登出

return "redirect:login";

}

}

4.6 关于shiro的开发

a、自定义安全策略

MyShiroRealm.java

package com.qf.shiro;

import com.qf.domain.SysPermission;

import com.qf.domain.SysUser;

import com.qf.service.SysPermissionService;

import com.qf.service.SysUserService;

import org.apache.shiro.SecurityUtils;

import org.apache.shiro.authc.AuthenticationException;

import org.apache.shiro.authc.AuthenticationInfo;

import org.apache.shiro.authc.AuthenticationToken;

import org.apache.shiro.authc.SimpleAuthenticationInfo;

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;

import java.util.Collection;

import java.util.HashSet;

import java.util.List;

/**

* Created by 54110 on 2019-07-05.

*/

public class MyShiroRealm extends AuthorizingRealm {

@Autowired

private SysUserService sysUserServiceImpl;

@Autowired

private SysPermissionService sysPermissionServiceImpl;

private String username;

// 系统授权

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

Subject subject = SecurityUtils.getSubject();//获取主体对象

String username =(String ) subject.getPrincipal();//获取用户身份信息

List permissions = sysPermissionService.queryPermissionsByLoginName(username);//根据用户名获取用户的权限信息

// 权限去重

Collection perms = new HashSet<>();

for (SysPermission perm: permissions ) {

perms.add(perm.getPermName());

}

SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

simpleAuthorizationInfo.addStringPermissions(perms);//授权

return simpleAuthorizationInfo;

}

//用户认证

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

String username = (String) token.getPrincipal();//获取用户信息

//根据用户信息查询数据库获取后端的用户身份,转交给securityManager判定

SysUser user1 = sysUserService.queryUserByLoginName(username);//从数据库直接取

System.out.println(user1);

if(user1!=null) {

SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user1.getLoginName(), user1.getPassword(), getName());

return simpleAuthenticationInfo;

}

return null;

}

}

b、自定义Shiro配置管理

ShiroConfig.java

package com.qf.config;

import com.qf.shiro.MyShiroRealm;

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

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

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

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

import java.util.Map;

/**

* Created by 54110 on 2019-07-05.

*/

@Configuration

public class ShiroConfig {

@Bean

public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager){

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

Map map = new HashMap<>();

map.put("/main","authc"); //必须登录才可访问

map.put("/one","perms[user_edit]");//只有特定权限(“user_edit”)的用户登录后才可访问

map.put("/two","perms[user_forbidden]");//只有特定权限(“user_forbidden”)的用户登录后才可访问

shiroFilterFactoryBean.setLoginUrl("/login");//设置登录页(匿名)

shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");//权限不足的错误提示页

shiroFilterFactoryBean.setFilterChainDefinitionMap(map);//装配拦截策略

return shiroFilterFactoryBean;

}

// 配置安全管理器(注入Realm对象)

@Bean(name="defaultWebSecurityManager")

public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("myShiroRealm") MyShiroRealm myShiroRealm){

DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

defaultWebSecurityManager.setRealm(myShiroRealm);

return defaultWebSecurityManager;

}

@Bean(name="myShiroRealm") //使用该注解是的Realm对象由spring容器管理

public MyShiroRealm myShiroRealm(){

MyShiroRealm shiroRealm = new MyShiroRealm();

return shiroRealm;

}

}

五、功能测试

​ 地址栏输入http://localhost:8080/login

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BdyHhsoT-1604249108147)(img\shiro03.png)]

​ 使用用户:admin密码:admin登录,登录成功后显示页面如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdoaSOGM-1604249108149)(img/shiro04.png)]

​ 因为admin2用户拥有case one功能的操作权限,所以当鼠标单击case one链接时,显示如下成功访问页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Co1rUWJi-1604249108150)(img/shiro05.png)]

因为admin2没有case two访问权限,当用户单击case two时,会显示无权限访问的页面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geha4PCi-1604249108150)(img/shiro06.png)]

当单击logout链接时,系统重回登录页。此时使用用户test2密码test2再次登录。因test2用户无case one权限,有case two权限,所以当test2用户单击case two时会显示如下页面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AWZrOeGj-1604249108151)(img/shiro07.png)]

六、启动shiro注解模式

6.1、在ShiroConfig.java中注释掉原先使用路径过滤的权限拦截语句:

//只有特定权限(“user_edit”)的用户登录后才可访问

// map.put("/one","perms[user_edit]");

//只有特定权限(“user_forbidden”)的用户登录后才可访问

// map.put("/two","perms[user_forbidden]");

6.2、修改ShiroConfig.java类代码添加如下内容:

/**

* 开启Shiro注解(如@RequiresRoles,@RequiresPermissions),

* 需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证

* 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)

*/

@Bean

public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){

DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

advisorAutoProxyCreator.setProxyTargetClass(true);

return advisorAutoProxyCreator;

}

/**

* 开启aop注解支持

*/

@Bean

public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {

AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();

authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);

return authorizationAttributeSourceAdvisor;

}

6.3、修改UserController.java控制器接口代码:

// 登录且拥有user:

@RequiresPermissions(value={"user_edit"})

@RequestMapping("/one")

public String showCaseOne(){

return "one";

}

@RequiresPermissions(value={"user_forbidden"})

@RequestMapping("/two")

public String showCaseTwo(){

return "two";

}

6.4、测试

此事有权限访问的也页面正常,但未授权的页面,无法进入提示页,显示如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pM9lRu2Z-1604249108152)(img/shiro08.png)]

​ 后台亦抛出org.apache.shiro.authz.AuthorizationException异常:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k8KTUuoi-1604249108152)(img/shiro09.png)]

6.5、此时使用aop拦截抛出的异常

package com.jeffrey.exception;

import org.apache.shiro.authz.UnauthorizedException;

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

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

import javax.servlet.http.HttpServletRequest;

/**

* Created by jeffrey on 2019/4/8.

*/

@ControllerAdvice

public class ExceptionController {

@ExceptionHandler(value = UnauthorizedException.class)//处理访问方法时权限不足问题

public String defaultErrorHandler(HttpServletRequest req, Exception e) {

return "unauth";

}

}

七、shiro密码的MD5加密处理

7.1.密码的加密

在数据表中存的密码不应该是12345,而应该是12345加密之后的字符串,而且还要求这个加密算法是不可逆的,即由加密后的字符串不能反推回来原来的密码,如果能反推回来那这个加密是没有意义的。

著名的加密算法,比如 MD5,SHA1

7.2.MD5加密

1). 如何把一个字符串加密为MD5

2). 使用MD5加密算法后,前台用户输入的字符串如何使用MD5加密,需要做的是将当前的Realm 的credentialsMatcher属性,替换为Md5CredentialsMatcher 由于Md5CredentialsMatcher已经过期了,推荐使用HashedCredentialsMatcher 并设置加密算法即可。

7.3.使用MD5加密

1). 修改ShiroConfig.java文件添加如下内容;

/**

* 密码校验规则HashedCredentialsMatcher

* 这个类是为了对密码进行编码的 ,

* 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,

* 这个类也负责对form里输入的密码进行编码

* 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher

*/

@Bean("hashedCredentialsMatcher")

public HashedCredentialsMatcher hashedCredentialsMatcher() {

HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();

//指定加密方式为MD5

credentialsMatcher.setHashAlgorithmName("MD5");

//加密次数

credentialsMatcher.setHashIterations(1024);

credentialsMatcher.setStoredCredentialsHexEncoded(true);

return credentialsMatcher;

}

@Bean("myShiroRealm")

public MyShiroRealm myShiroRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {

MyShiroRealm authRealm = new MyShiroRealm();

authRealm.setAuthorizationCachingEnabled(false);

authRealm.setCredentialsMatcher(matcher);

return authRealm;

}

​ 2).修改MyRealm.java的认证逻辑如下:

//用户认证

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

String username = (String) token.getPrincipal();//获取用户信息

SysUser user1 = sysUserService.queryUserByLoginName(username);//从数据库直接取

System.out.println(user1);

if(user1!=null) {

//当前realm对象的name

String realmName = getName();

//盐值

ByteSource credentialsSalt = ByteSource.Util.bytes(username);

//封装用户信息,构建AuthenticationInfo对象并返回

AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(username, user1.getPassword(), credentialsSalt, realmName);

return authcInfo;

}

return null;

}

​ 3). 通过 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 我们可以得到"12345"经过MD5 加密1024后的字符串;

package com.jeffrey;

import org.apache.shiro.crypto.hash.SimpleHash;

import org.apache.shiro.util.ByteSource;

/**

* Created by jeffrey on 2019/4/8.

*/

public class MD5Salt {

public static void main(String[] args){

String hashAlgorithName = "MD5";//加密算法

String password = "12345";//登陆时的密码

int hashIterations =1024;//加密次数

ByteSource credentialsSalt = ByteSource.Util.bytes("admin2");//使用登录名做为salt

SimpleHash simpleHash = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);

System.out.println("ok "+simpleHash);

}

}

​ 使用密文替换数据库中的明文密码;

7.4 测试 略。。。。

7.5 后记 为什么使用 MD5 盐值加密: 希望即使两个原始密码相同,加密得到的两个字符串也不同。

为什么使用 MD5 盐值加密:

希望即使两个原始密码相同,加密得到的两个字符串也不同。

如何做到:

1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器

2). 使用 ByteSource.Util.bytes() 来计算盐值.

3). 盐值需要唯一: 一般使用随机字符串或 user id

4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值.


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:如何基于mybatis框架查询数据库表数据并打印
下一篇:Spring Boot 如何整合连接池
相关文章

 发表评论

暂时没有评论,来抢沙发吧~