搭建SpringBoot基本框架
image.pngimage.png
pom.xml导入MyBatis Plus依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
application.yml添加数据源配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiroadmin?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
创建数据库
image.pngimage.png
image.png
image.png
使用mybatis plus代码生成器生成entity
1.添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.7</version>
</dependency>
2.运行Main方法
public class Main {
public static void main(String[] args) {
//创建generator对象
AutoGenerator autoGenerator = new AutoGenerator();
//数据源
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL);
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/shiroadmin?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("root");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
autoGenerator.setDataSource(dataSourceConfig);
//全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir")+"/src/main/java");
//创建好之后打开文件
globalConfig.setOpen(false);
globalConfig.setAuthor("Jesse");
globalConfig.setServiceName("%sService");
autoGenerator.setGlobalConfig(globalConfig);
//包信息
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.glg.shiroadmin");
packageConfig.setController("controller");
packageConfig.setService("service");
packageConfig.setServiceImpl("service.impl");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
autoGenerator.setPackageInfo(packageConfig);
//配置策略
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig.setEntityLombokModel(true);
strategyConfig.setTablePrefix("tb_");
//开启字段驼峰命名
strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
autoGenerator.setStrategy(strategyConfig);
autoGenerator.execute();
}
}
MyBatis Plus查询功能测试
@Controller
public class UserController {
@Autowired
UserService userService;
@GetMapping("/userList")
@ResponseBody
public List<User> getUserList(){
List<User> userList = userService.getAllUser();
return userList;
}
添加用户角色和权限查询方法
根据用户名查询角色 findRoleByUser,
根据对应的角色查询权限 findPermissionByRole
public interface RoleMapper extends BaseMapper<Role> {
//根据用户查询角色
List<Role> findRoleByUser(@Param("user") User user);
}
在resource下创建mapper包,编写RoleMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.glg.shiroadmin.mapper.RoleMapper">
<resultMap id="BaseResultMap" type="com.glg.shiroadmin.entity.Role">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="role_name" jdbcType="VARCHAR" property="roleName" />
<result column="remark" jdbcType="VARCHAR" property="remark" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="modified_time" jdbcType="TIMESTAMP" property="modifiedTime" />
<result column="status" jdbcType="INTEGER" property="status" />
</resultMap>
<select id="findRoleByUser" resultType="com.glg.shiroadmin.entity.Role" parameterType="com.glg.shiroadmin.entity.User">
select
r.id,r.role_name roleName,r.remark,r.create_time createTime,r.modified_time modifiedTime,r.status
from
tb_user u
LEFT JOIN tb_user_role ur ON u.id = ur.user_id
LEFT JOIN tb_role r ON r.id = ur.role_id
<where>
1 = 1
<if test="user != null and user != '' ">
AND u.username = #{user.username}
</if>
</where>
</select>
</mapper>
public interface MenuMapper extends BaseMapper<Menu> {
//根据角色名查权限
List<Menu> findPermissionByRole(@Param("role") Role role);
}
resource/mapper创建MenuMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.glg.shiroadmin.mapper.MenuMapper">
<resultMap id="BaseResultMap" type="com.glg.shiroadmin.entity.Menu">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="parent_id" jdbcType="BIGINT" property="parentId" />
<result column="menu_name" jdbcType="VARCHAR" property="menuName" />
<result column="url" jdbcType="VARCHAR" property="url" />
<result column="perms" jdbcType="VARCHAR" property="perms" />
<result column="icon" jdbcType="VARCHAR" property="icon" />
<result column="type" jdbcType="CHAR" property="icon" />
<result column="order_num" jdbcType="BIGINT" property="orderNum" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="modified_time" jdbcType="TIMESTAMP" property="modifiedTime" />
<result column="available" jdbcType="INTEGER" property="available" />
<result column="open" jdbcType="INTEGER" property="open" />
</resultMap>
<select id="findPermissionByRole" resultType="com.glg.shiroadmin.entity.Menu" parameterType="com.glg.shiroadmin.entity.Role">
SELECT
p.id,
p.perms,
p.url
from
tb_role r
LEFT JOIN tb_role_menu rp ON r.id = rp.role_id
LEFT JOIN tb_menu p ON rp.menu_id = p.id
<where>
1 = 1
<if test="role != null and role != '' ">
AND r.role_name = #{role.roleName}
</if>
</where>
</select>
</mapper>
测试:
@Test
void testt(){
User user = new User();
user.setUsername("admin");
roleMapper.findRoleByUser(user).forEach(System.out::println);
}
image.png
@Test
void test(){
Role role = new Role();
role.setRoleName("超级管理员");
menuMapper.findPermissionByRole(role).forEach(System.out::println);
}
image.png
开始添加Shiro
1.pom.xml添加shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
自定义Shiro过滤器
2.创建一个realm包 ,编写UserRealm类
AuthorizationInfo 授权
AuthenticationInfo 认证
1.先在AuthenticationInfo 认证,再在AuthorizationInfo授权
2.此处认证方式使用Shiro给账号密码 加盐、加密。
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.glg.shiroadmin.entity.Menu;
import com.glg.shiroadmin.entity.Role;
import com.glg.shiroadmin.entity.User;
import com.glg.shiroadmin.mapper.MenuMapper;
import com.glg.shiroadmin.mapper.RoleMapper;
import com.glg.shiroadmin.mapper.UserMapper;
import com.glg.shiroadmin.service.UserService;
import com.glg.shiroadmin.utils.AesCipherUtil;
import org.apache.shiro.SecurityUtils;
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.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private MenuMapper menuMapper;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//1.获取当前用户信息,
Subject subject = SecurityUtils.getSubject();
//获取用户信息
User user = (User) subject.getPrincipal();
//设置角色
Set<String> roles = new HashSet<>();
//根据用户查询角色
List<Role> roleList = roleMapper.findRoleByUser(user);
System.out.println(user.getUsername()+"拥有的角色有"+roleList.toString());
for (Role role : roleList){
if (role != null){
//添加用户角色
simpleAuthorizationInfo.addRole(role.getRoleName());
//根据用户角色查询权限
List<Menu> menuList = menuMapper.findPermissionByRole(role);
System.out.println(role.getRoleName()+"角色拥有的权限有"+menuList.toString());
for(Menu menu : menuList){
if(menu!=null){
simpleAuthorizationInfo.addStringPermission(menu.getPerms());
}
}
}
}
return simpleAuthorizationInfo;
}
//认证
/**
* 执行subject.login(token);这个方法的时候shiro就会直接到我们自己创建的Realm类中寻找
* doGetAuthenticationInfo方法,在这个方法里我们使用如下方式来验证数据库中的密码
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.先认证
/**客户端传来的用户名封装在token里,根据用户名进行查询,如果不存在return null,shiro自动抛出账户不存在的异常。
* 不为空,返回SimpleAuthenticationInfo对象,对象里传入数据库查询的密码和token的得到的密码。自动验证,
*如果密码不对,shiro也会抛出异常 我们只需要捕获这两个异常
*/
String userName = (String) token.getPrincipal();
if (userName == null || userName.length() == 0) { return null; }
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("username",userName);
User userInfo = userMapper.selectOne(wrapper);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
ByteSource.Util.bytes(userInfo.getSalt()), //盐
getName() //realm name
);
return authenticationInfo;
}
}
3.编写配置ShiroConfig.java
ShiroFilterFactoryBean 一个ShiroFilter,一般配置需要安全控制的URL。
DefaultWebSecurityManager 主要定义了设置subjectDao,获取会话模式,设置会话模式,设置会话管理器,是否是http会话模式等操作,它继承了DefaultSecurityManager类,实现了WebSecurityManager接口,现对其解析如下:
1.WebSecurityManager接口
可以参考WebSecurityManager接口源码解析,里面只有一个方法,定义了是否http会话模式。
2.DefaultSecurityManager类
可以参考DefaultSecurityManager类源码解析,里面主要定义了登录,创建subject,登出等操作。
3.DefaultWebSecurityManager类
HashedCredentialsMatcher 凭证匹配器 密码校验交给Shiro的SimpleAuthenticationInfo进行处理
UserRealm setCredentialsMatcher(hashedCredentialsMatcher());
import com.glg.shiroadmin.realm.UserRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Hashtable;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
//权限设置
Map<String,String> map = new Hashtable<>();
map.put("/public","anon");
map.put("/userList","authc");
map.put("/systemadmin", "perms[system]");
map.put("/menusystem", "perms[menu]");
map.put("/usersystem", "perms[users]");
factoryBean.setFilterChainDefinitionMap(map);
//设置登录页面
// factoryBean.setLoginUrl("/login");
//设置未授权页面
factoryBean.setUnauthorizedUrl("/unauth");
return factoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(userRealm);
return manager;
}
/**
* 凭证匹配器
* 密码校验交给Shiro的SimpleAuthenticationInfo进行处理
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数;
return hashedCredentialsMatcher;
}
//MD5加密
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*/
/* 下面的代码是添加注解支持
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 强制使用cglib,防止重复代理和可能引起代理出错的问题,https://zhuanlan.zhihu.com/p/29161098
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
认证以及授权完成,接下来编写登录,注册,退出的controller
此注册功能处,用户数据表中注册时间以及修改时间可设置为MybatisPlus自动填充
1.在实体类,添加注解
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date modifiedTime;
2.编写handler.java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("modifiedTime",new Date(),metaObject);}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("modifiedTime",new Date(),metaObject);}}
3.开始写登录注册的controller
/**
* 登录方法
*/
@PostMapping("/login")
@ResponseBody
public ResultTypeDTO login(@RequestBody User user){
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("username",user.getUsername());
List<User> userList = userMapper.selectList(wrapper);
if(userList.size()==0){ return new ResultTypeDTO().errorOf(CustomizeErrorCode.USER_NOT_FOUND); }
//账号密码令牌
AuthenticationToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
//获得当前用户到登录对象,现在状态为未认证
Subject subject = SecurityUtils.getSubject();
try {
//将令牌传到shiro提供的login方法验证,需要自定义realm
subject.login(token);
User logineduser = (User) subject.getPrincipal();
subject.getSession().setAttribute("logineduser",logineduser);
return new ResultTypeDTO().okOf().addMsg("message","登陆成功");
}catch (UnknownAccountException e){
e.printStackTrace();
return new ResultTypeDTO().errorOf(CustomizeErrorCode.USER_NOT_FOUND);
}catch (IncorrectCredentialsException e){
e.printStackTrace();
return new ResultTypeDTO().errorOf(CustomizeErrorCode.LOGIN_FAIL);
}
}
@GetMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
/**
* 用户注册
*/
@PostMapping("/register")
@ResponseBody
public ResultTypeDTO userRegister(@RequestBody User user){
// 查询数据库中的帐号信息 判断当前帐号是否存在
QueryWrapper wrapper = new QueryWrapper();
wrapper.eq("username",user.getUsername());
List<User> userList = userMapper.selectList(wrapper);
if (userList.size() != 0 && StringUtil.isNotBlank(userList.get(0).getPassword())) {
return new ResultTypeDTO().errorOf(CustomizeErrorCode.THIS_USER_USED); }
String originalPassword = user.getPassword(); //原始密码
String hashAlgorithmName = "MD5"; //加密方式
int hashIterations = 2; //加密的次数
//盐
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
//加密
SimpleHash simpleHash = new SimpleHash(hashAlgorithmName, originalPassword, salt, hashIterations);
String encryptionPassword = simpleHash.toString();
//创建用户信息
user.setPassword(encryptionPassword);
user.setSalt(salt);
user.setStatus(1);
//执行新增
int count = userMapper.insert(user);
if (count <= 0) {
throw new CustomException("新增失败(Insert Failure)");
}
/** 根据user添加角色
* user.getId() 直接取插入新数据的id
*/
int flag = userRoleMapper.insertUserRole(user.getId(),user.getType());
if(flag <= 0){
throw new CustomException("新增失败(Insert Failure)");
}
return new ResultTypeDTO().okOf();
}
userRoleMapper.java
package com.glg.shiroadmin.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.glg.shiroadmin.entity.User_role;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
public interface userRoleMapper extends BaseMapper<User_role> {
@Insert("insert into tb_user_role(user_id,role_id) values(#{uid},#{rid})")
int insertUserRole(@Param("uid") Long uid, @Param("rid")Integer rid);
}
注册 登录测试
=========================
注册不同角色的用户,进行认证授权测试
图片.pngtype=1:系统管理员
type=2:功能管理员
type=3:用户管理员
访问如下url进行测试
@Controller
public class AdminController {
@Autowired
private UserService userService;
//开放接口 无需登录
@GetMapping("/public")
@ResponseBody
public String open(){
return "这是open权限接口";
}
@GetMapping("/unauth")
@ResponseBody
public ResultTypeDTO unauth(){
return new ResultTypeDTO().errorOf(CustomizeErrorCode.USER_NOT_PERMISSION);
}
//只需登录
@GetMapping("/userList")
@ResponseBody
public List<User> getUserList(){
List<User> userList = userService.getAllUser();
return userList;
}
//系统管理员才能访问
@GetMapping("/systemadmin")
@ResponseBody
public String systemadmin(){
return "这是systemadmin权限接口,拥有系统管理权限才能访问";
}
//功能模块管理员才能访问
@GetMapping("/menusystem")
@ResponseBody
public String menusystem(){
return "这是menusystem权限接口,拥有功能模块管理权限才能访问";
}
//用户管理员才能访问
@GetMapping("/usersystem")
@ResponseBody
public String usersystem(){
return "这是usersystem权限接口,拥有用户管理权限才能访问";
}
}
网友评论