洞察如何通过低成本家政服务app实现高效管理与数字化转型
566
2023-03-18
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
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
}
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
List
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
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
// 权限去重
Collection
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.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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~