Shiro实战
shiro实战内容包括三个部分:(1)shiro后台表结构,用于存储shiro对应的用户、角色、权限及关联关系。(2)后端代码,及基于shiro配置用户、角色、权限及对应关系以及登录、认证。(3)前端代码,维护shiro信息及登录、认证。
这篇博文我们介绍第二部分,即后端设计
后端代码
后端数据库访问用的mybatis及lombok插件。
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${spring.shiro.version}</version>
</dependency>
<dependency>
<!--session持久化插件-->
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>${shiro.redis.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 导入配置文件处理器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
配置文件
主要配置数据库连接、mybatis及shiro信息。
spring:
datasource:
url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
type-aliases-package: cc.lisen.shiro.entity
mapper-locations: classpath:mapper/*.xml
shiro:
user:
loginUrl: /shiro/login/**
unauthorizedUrl: /shiro/logout
indexUrl: /shiro/index
captchaEnabled: true
captchaType: math
redis:
host: localhost
port: 6379
cookie:
domain:
path: /
httpOnly: false
maxAge: 72 #Hours,利用 cookie 免登录。
secure: false
session:
expireTime: 72 #Hours
dbSyncPeriod: 1
validationInterval: 10
maxSession: -1
kickoutAfter: false
server:
port: 8888
#配置跨域
cors:
allowedOrigin:
- http://localhost:8080
allowCredentials:
true
allowHeaders:
- \*
allowMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
maxAge:
7200
path:
/**
mybatis
代码配置完成后,我们先生成mybatis代码,完成mybatis的操作。
我使用Free MyBatis plugin
插件生成一些代码。
mybatis实体
ShiroUser.java
@Data
public class ShiroUser implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String userCode;
private String userName;
private String password;
private Set<ShiroRole> shiroRoleSet;
}
ShiroRole.java
@Data
public class ShiroRole implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String roleCode;
private String roleName;
private Set<ShiroPermission> shiroPermissionSet;
}
ShiroPermission.java
@Data
public class ShiroPermission implements Serializable {
private Long id;
private String permissionCode;
private String permissionName;
private static final long serialVersionUID = 1L;
}
ShiroUserRole.java
@Data
public class ShiroUserRole implements Serializable {
private Long id;
private Long userId;
private Long roleId;
private static final long serialVersionUID = 1L;
}
ShiroRolePermission.java
@Data
public class ShiroRolePermission implements Serializable {
private Long id;
private Long roleId;
private Long permissionId;
private static final long serialVersionUID = 1L;
}
数据访问接口
ShiroUserMapper.java
@Mapper
public interface ShiroUserMapper {
int deleteByPrimaryKey(Long id);
int insert(ShiroUser record);
int insertSelective(ShiroUser record);
/**
* 根据主键获取用户信息
* @param id Id
* @return 用户
*/
ShiroUser selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(ShiroUser record);
int updateByPrimaryKey(ShiroUser record);
/**
* 根据用户编号获取
* @param userCode 用户编号
* @return 用户
*/
ShiroUser findUserByUserCode(String userCode);
}
ShiroRoleMapper.java
@Mapper
public interface ShiroRoleMapper {
int deleteByPrimaryKey(Long id);
int insert(ShiroRole record);
int insertSelective(ShiroRole record);
ShiroRole selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(ShiroRole record);
int updateByPrimaryKey(ShiroRole record);
/**
* 根据用户编号获取其角色列表
* @param userCode 用户编号
* @return 权限列表
*/
Set<ShiroRole> findByUserCode(String userCode);
}
ShiroPermissionMapper.java
@Mapper
public interface ShiroPermissionMapper {
int deleteByPrimaryKey(Long id);
int insert(ShiroPermission record);
int insertSelective(ShiroPermission record);
ShiroPermission selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(ShiroPermission record);
int updateByPrimaryKey(ShiroPermission record);
/**
* 获取角色对应权限
* @param roleId 角色Id
* @return 权限集合
*/
Set<ShiroPermission> findByRoleId(long roleId);
}
ShiroUserRoleMapper.java
@Mapper
public interface ShiroUserRoleMapper {
int deleteByPrimaryKey(Long id);
int insert(ShiroUserRole record);
int insertSelective(ShiroUserRole record);
ShiroUserRole selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(ShiroUserRole record);
int updateByPrimaryKey(ShiroUserRole record);
}
ShiroRolePermissionMapper.java
@Mapper
public interface ShiroRolePermissionMapper {
int deleteByPrimaryKey(Long id);
int insert(ShiroRolePermission record);
int insertSelective(ShiroRolePermission record);
ShiroRolePermission selectByPrimaryKey(Long id);
int updateByPrimaryKeySelective(ShiroRolePermission record);
int updateByPrimaryKey(ShiroRolePermission record);
}
xml文件
ShiroUserMapper.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="cc.lisen.shiro.mapper.ShiroUserMapper">
<resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroUser">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="userCode" jdbcType="VARCHAR" property="userCode"/>
<result column="userName" jdbcType="VARCHAR" property="userName"/>
<result column="password" jdbcType="VARCHAR" property="password"/>
</resultMap>
<sql id="Base_Column_List">
id, userCode, userName, `password`
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from Shiro_User
where id = #{id,jdbcType=BIGINT}
</select>
<select id="findUserByUserCode" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from Shiro_User where userCode = #{userCode}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete
from Shiro_User
where id = #{id,jdbcType=BIGINT}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUser"
useGeneratedKeys="true">
insert into Shiro_User (userCode, userName, `password`)
values (#{usercode,jdbcType=VARCHAR}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUser"
useGeneratedKeys="true">
insert into Shiro_User
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userCode != null">
userCode,
</if>
<if test="userName != null">
userName,
</if>
<if test="password != null">
`password`,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userCode != null">
#{usercode,jdbcType=VARCHAR},
</if>
<if test="userName != null">
#{username,jdbcType=VARCHAR},
</if>
<if test="password != null">
#{password,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroUser">
update Shiro_User
<set>
<if test="userCode != null">
userCode = #{usercode,jdbcType=VARCHAR},
</if>
<if test="userName != null">
userName = #{username,jdbcType=VARCHAR},
</if>
<if test="password != null">
`password` = #{password,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
<update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroUser">
update Shiro_User
set userCode = #{usercode,jdbcType=VARCHAR},
userName = #{username,jdbcType=VARCHAR},
`password` = #{password,jdbcType=VARCHAR}
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
ShiroRoleMapper.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="cc.lisen.shiro.mapper.ShiroRoleMapper">
<resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroRole">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="roleCode" jdbcType="VARCHAR" property="roleCode" />
<result column="roleName" jdbcType="VARCHAR" property="roleName" />
</resultMap>
<sql id="Base_Column_List">
id, roleCode, roleName
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from Shiro_Role
where id = #{id,jdbcType=BIGINT}
</select>
<select id="findByUserCode" resultType="cc.lisen.shiro.entity.ShiroRole">
select Shiro_Role.* from Shiro_Role inner join Shiro_User_Role SUR on Shiro_Role.id = SUR.roleId
inner join Shiro_User SU on SUR.userId = SU.id
where su.userCode = #{userCode}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from Shiro_Role
where id = #{id,jdbcType=BIGINT}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRole" useGeneratedKeys="true">
insert into Shiro_Role (roleCode, roleName)
values (#{rolecode,jdbcType=VARCHAR}, #{rolename,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRole" useGeneratedKeys="true">
insert into Shiro_Role
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="roleCode != null">
roleCode,
</if>
<if test="roleName != null">
roleName,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="roleCode != null">
#{rolecode,jdbcType=VARCHAR},
</if>
<if test="roleName != null">
#{rolename,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroRole">
update Shiro_Role
<set>
<if test="roleCode != null">
roleCode = #{rolecode,jdbcType=VARCHAR},
</if>
<if test="roleName != null">
roleName = #{rolename,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
<update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroRole">
update Shiro_Role
set roleCode = #{rolecode,jdbcType=VARCHAR},
roleName = #{rolename,jdbcType=VARCHAR}
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
ShiroPermissionMapper.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="cc.lisen.shiro.mapper.ShiroPermissionMapper">
<resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroPermission">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="permissionCode" jdbcType="VARCHAR" property="permissionCode"/>
<result column="permissionName" jdbcType="VARCHAR" property="permissionName"/>
</resultMap>
<sql id="Base_Column_List">
id, permissionCode, permissionName
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from Shiro_Permission
where id = #{id,jdbcType=BIGINT}
</select>
<select id="findByRoleId" resultType="cc.lisen.shiro.entity.ShiroPermission">
select Shiro_Permission.*
from Shiro_Permission
inner join Shiro_Role_Permission SRP on Shiro_Permission.id = SRP.permissionId
where SRP.roleId = #{roleId}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete
from Shiro_Permission
where id = #{id,jdbcType=BIGINT}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroPermission"
useGeneratedKeys="true">
insert into Shiro_Permission (permissionCode, permissionName)
values (#{permissioncode,jdbcType=VARCHAR}, #{permissionname,jdbcType=VARCHAR})
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id"
parameterType="cc.lisen.shiro.entity.ShiroPermission" useGeneratedKeys="true">
insert into Shiro_Permission
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="permissionCode != null">
permissionCode,
</if>
<if test="permissionName != null">
permissionName,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="permissionCode != null">
#{permissioncode,jdbcType=VARCHAR},
</if>
<if test="permissionName != null">
#{permissionname,jdbcType=VARCHAR},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroPermission">
update Shiro_Permission
<set>
<if test="permissionCode != null">
permissionCode = #{permissioncode,jdbcType=VARCHAR},
</if>
<if test="permissionName != null">
permissionName = #{permissionname,jdbcType=VARCHAR},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
<update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroPermission">
update Shiro_Permission
set permissionCode = #{permissioncode,jdbcType=VARCHAR},
permissionName = #{permissionname,jdbcType=VARCHAR}
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
ShiroUserRoleMapper.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="cc.lisen.shiro.mapper.ShiroUserRoleMapper">
<resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroUserRole">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="userId" jdbcType="BIGINT" property="userId" />
<result column="roleId" jdbcType="BIGINT" property="roleId" />
</resultMap>
<sql id="Base_Column_List">
id, userId, roleId
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from Shiro_User_Role
where id = #{id,jdbcType=BIGINT}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from Shiro_User_Role
where id = #{id,jdbcType=BIGINT}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUserRole" useGeneratedKeys="true">
insert into Shiro_User_Role (userId, roleId)
values (#{userid,jdbcType=BIGINT}, #{roleid,jdbcType=BIGINT})
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUserRole" useGeneratedKeys="true">
insert into Shiro_User_Role
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="userId != null">
userId,
</if>
<if test="roleId != null">
roleId,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userid != null">
#{userid,jdbcType=BIGINT},
</if>
<if test="roleId != null">
#{roleid,jdbcType=BIGINT},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroUserRole">
update Shiro_User_Role
<set>
<if test="userId != null">
userId = #{userid,jdbcType=BIGINT},
</if>
<if test="roleId != null">
roleId = #{roleid,jdbcType=BIGINT},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
<update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroUserRole">
update Shiro_User_Role
set userId = #{userid,jdbcType=BIGINT},
roleId = #{roleid,jdbcType=BIGINT}
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
ShiroRolePermissionMapper.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="cc.lisen.shiro.mapper.ShiroRolePermissionMapper">
<resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroRolePermission">
<id column="id" jdbcType="BIGINT" property="id" />
<result column="roleId" jdbcType="BIGINT" property="roleId" />
<result column="permissionId" jdbcType="BIGINT" property="permissionId" />
</resultMap>
<sql id="Base_Column_List">
id, roleId, permissionId
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from Shiro_Role_Permission
where id = #{id,jdbcType=BIGINT}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from Shiro_Role_Permission
where id = #{id,jdbcType=BIGINT}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRolePermission" useGeneratedKeys="true">
insert into Shiro_Role_Permission (roleId, permissionId)
values (#{roleid,jdbcType=BIGINT}, #{permissionid,jdbcType=BIGINT})
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRolePermission" useGeneratedKeys="true">
insert into Shiro_Role_Permission
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="roleId != null">
roleId,
</if>
<if test="permissionId != null">
permissionId,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="roleId != null">
#{roleid,jdbcType=BIGINT},
</if>
<if test="permissionId != null">
#{permissionid,jdbcType=BIGINT},
</if>
</trim>
</insert>
<update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroRolePermission">
update Shiro_Role_Permission
<set>
<if test="roleId != null">
roleId = #{roleid,jdbcType=BIGINT},
</if>
<if test="permissionId != null">
permissionId = #{permissionid,jdbcType=BIGINT},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
<update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroRolePermission">
update Shiro_Role_Permission
set roleId = #{roleid,jdbcType=BIGINT},
permissionId = #{permissionid,jdbcType=BIGINT}
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>
服务层
服务层这里忽略了接口,有需要的可以查看源代码
ShiroUserServiceImpl.java
@Service
public class ShiroUserServiceImpl implements ShiroUserService {
@Resource
ShiroUserMapper shiroUserMapper;
@Resource
ShiroRoleService shiroRoleService;
/**
* 根据用户编号获取
*
* @param userCode 用户编号
* @return 用户
*/
@Override
public ShiroUser findUserByUserCode(String userCode) {
ShiroUser shiroUser = shiroUserMapper.findUserByUserCode(userCode);
Set<ShiroRole> shiroRoleSet = shiroRoleService.findByUserCode(userCode);
shiroUser.setShiroRoleSet(shiroRoleSet);
return shiroUser;
}
}
ShiroRoleServiceImpl.java
@Service
public class ShiroRoleServiceImpl implements ShiroRoleService {
@Resource
ShiroRoleMapper shiroRoleMapper;
@Resource
ShiroPermissionService shiroPermissionService;
/**
* 根据用户编号获取其角色列表
*
* @param userCode 用户编号
* @return 权限列表
*/
@Override
public Set<ShiroRole> findByUserCode(String userCode) {
Set<ShiroRole> shiroRoleSet = shiroRoleMapper.findByUserCode(userCode);
for (ShiroRole shiroRole : shiroRoleSet) {
Set<ShiroPermission> shiroPermissionSet = shiroPermissionService.findByRoleId(shiroRole.getId());
shiroRole.setShiroPermissionSet(shiroPermissionSet);
}
return shiroRoleSet;
}
}
ShiroPermissionServiceImpl.java
@Service
public class ShiroPermissionServiceImpl implements ShiroPermissionService {
@Resource
ShiroPermissionMapper shiroPermissionMapper;
/**
* 获取角色对应权限
*
* @param roleId 角色Id
* @return 权限集合
*/
@Override
public Set<ShiroPermission> findByRoleId(long roleId) {
return shiroPermissionMapper.findByRoleId(roleId);
}
}
ShiroServiceImpl.java
@Service
public class ShiroServiceImpl implements ShiroService {
@Resource
ShiroUserService shiroUserService;
/**
* 根据用户编号获取
*
* @param userCode 用户编号
* @return 用户
*/
@Override
public ShiroUser findUserByCode(String userCode) {
return shiroUserService.findUserByUserCode(userCode);
}
}
shiro配置
常量配置
AuthenConst
常量类,用于配置加密方式、盐、加密次数等信息。
/**
* @author laughing
* @date 2020/10/12
* @site https://lisen.cc
*/
public class AuthenConst {
public static final String SALT = "LiSen";
public static final String HASH_ALGORITHM_NAME = "MD5";
public static final int HASH_INTERACTIONS = 1024;
public static final String OPTION_REQUEST_NAME = "OPTIONS";
}
property映射
/**
* @author laughing
* @date 2020/10/11
* @site https://lisen.cc
*/
@ConfigurationProperties(prefix = "shiro")
@Configuration
@Data
public class ShiroProperty {
private User user;
private Redis redis;
}
@Data
class User{
private String loginUrl;
private String unauthorizedUrl;
private String indexUrl;
private boolean captchaEnabled;
private String captchaType;
}
@Data
class Redis{
private String host;
private long port;
}
自定义realm
/**
* @author laughing
* @date 2020/10/11
* @site https://www.lisen.org
*/
public class MyAuthenRealm extends AuthorizingRealm {
@Resource
ShiroService shiroService;
/**
* 认证
*
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userCode = token.getUsername();
ShiroUser shiroUser = shiroService.findUserByCode(userCode);
if (shiroUser == null) {
throw new AuthenticationException("账户不存在");
}
//自定义盐值
// ByteSource salt = ByteSource.Util.bytes(AuthenConst.SALT);
// String password = new SimpleHash(AuthenConst.HASH_ALGORITHM_NAME, new String(token.getPassword()), AuthenConst.SALT, AuthenConst.HASH_INTERACTIONS).toHex();
if (!new String(token.getPassword()).equals(shiroUser.getPassword())) {
throw new IncorrectCredentialsException("账户密码不正确");
}
// Subject subject = SecurityUtils.getSubject();
// ShiroUserVO shiroUserVO = new ShiroUserVO();
// BeanUtils.copyProperties(shiroUserVO,shiroUser);
// shiroUserVO.setSessionId(subject.getSession().getId().toString());
return new SimpleAuthenticationInfo(userCode, shiroUser.getPassword(), getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
if (StringUtils.isEmpty(principalCollection)) {
return null;
}
String userCode = principalCollection.getPrimaryPrincipal().toString();
ShiroUser shiroUser = shiroService.findUserByCode(userCode);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
for (ShiroRole shiroRole : shiroUser.getShiroRoleSet()) {
simpleAuthorizationInfo.addRole(shiroRole.getRoleCode());
for (ShiroPermission shiroPermission : shiroRole.getShiroPermissionSet()) {
simpleAuthorizationInfo.addStringPermission(shiroPermission.getPermissionCode());
}
}
return simpleAuthorizationInfo;
}
}
禁止未登录跳转
因为我们是前后端分离项目,如果未登陆跳转的话,前端无法捕捉重定向后的消息,所以我们需要配置禁止未登录跳转。
/**
* 对于跨域的POST请求,浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用,
* OPTIONS请求通过后继续执行POST请求,而shiro自带的权限验证是无法处理OPTIONS请求的,
* 所以这里需要重写isAccessAllowed方法
* @author laughing
* @date 2020/10/12
* @site https://lisen.cc
*/
public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter {
private static final Logger log = LoggerFactory.getLogger(ShiroFormAuthenticationFilter.class);
/**
* 重写是否允许访问
* @param request
* @param response
* @param mappedValue
* @return
*/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
if(AuthenConst.OPTION_REQUEST_NAME.equals(httpServletRequest.getMethod())){
return true;
}
return super.isAccessAllowed(request, response, mappedValue);
}
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws JSONException {
PrintWriter out = null;
HttpServletResponse res = (HttpServletResponse) response;
try {
res.setCharacterEncoding("UTF-8");
res.setContentType("application/json");
out = response.getWriter();
out.println("未授权");
} catch (Exception e) {
} finally {
if (null != out) {
out.flush();
out.close();
}
}
return false;
}
}
shiro配置
/**
* @author laughing
* @date 2020/10/11
* @site https://lisen.cc
*/
@Configuration
public class ShiroConfig {
/**
* 自定义验证
* @return
*/
@Bean
MyAuthenRealm myAuthenRealm() {
MyAuthenRealm myAuthenRealm = new MyAuthenRealm();
// myAuthenRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myAuthenRealm;
}
@Resource
ShiroProperty shiroProperty;
/**
* 权限管理,配置主要是Realm的管理认证
*
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(myAuthenRealm());
// 自定义session管理 使用redis
defaultWebSecurityManager.setSessionManager(sessionManager());
return defaultWebSecurityManager;
}
/**
* Filter工厂,设置对应的过滤条件和跳转条件
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
Map<String, String> filterChainDefinitionMap = new HashMap<>();
//登出
filterChainDefinitionMap.put(shiroProperty.getUser().getUnauthorizedUrl(), "logout");
//登录
shiroFilterFactoryBean.setLoginUrl(shiroProperty.getUser().getLoginUrl());
//首页
shiroFilterFactoryBean.setSuccessUrl(shiroProperty.getUser().getIndexUrl());
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put(shiroProperty.getUser().getLoginUrl(), "anon");
filterChainDefinitionMap.put(shiroProperty.getUser().getUnauthorizedUrl(), "anon");
filterChainDefinitionMap.put("/error", "anon");
filterChainDefinitionMap.put("/**", "authc");
LinkedHashMap<String, Filter> filtsMap = new LinkedHashMap<>();
shiroFilterFactoryBean.setFilters(filtsMap);
// 这里使用自定义的filter,禁止未登陆跳转
filtsMap.put("authc", new ShiroFormAuthenticationFilter());
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 加入注解的使用,不加入这个注解不生效
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
// @Bean
// public HashedCredentialsMatcher hashedCredentialsMatcher() {
// HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
// //指定加密方式
// credentialsMatcher.setHashAlgorithmName(AuthenConst.HASH_ALGORITHM_NAME);
// //加密次数
// credentialsMatcher.setHashIterations(AuthenConst.HASH_INTERACTIONS);
// //此处的设置,true加密用的hex编码,false用的base64编码
// credentialsMatcher.setStoredCredentialsHexEncoded(true);
// return credentialsMatcher;
// }
@Bean
public SessionManager sessionManager(){
MySessionManager mySessionManager = new MySessionManager();
// 取消登陆跳转URL后面的jsessionid参数
mySessionManager.setSessionIdUrlRewritingEnabled(false);
mySessionManager.setSessionDAO(redisSessionDAO());
// 不过期
mySessionManager.setGlobalSessionTimeout(-1);
return mySessionManager;
}
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager();
redisManager.setHost(shiroProperty.getRedis().getHost()+":"+shiroProperty.getRedis().getPort());
redisManager.setDatabase(0);
return redisManager;
}
@Bean
public RedisSessionDAO redisSessionDAO(){
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
}
全局异常
@RestControllerAdvice
public class MyAuthorizationException {
@ExceptionHandler(UnauthorizedException.class)
public String authorization(UnauthorizedException myAuthorizationException) {
return "没有"+myAuthorizationException.toString()+"权限";
}
}
测试类
/**
* @author laughing
* @date 2020/10/11
* @site https://www.lisen.org
*/
@RestController
@RequestMapping("/shiro")
public class ShiroController {
private final Logger logger = LoggerFactory.getLogger(ShiroController.class);
@Resource
ShiroService shiroService;
@RequestMapping("/findUserByUserCode")
public ShiroUser findUserByUserCode() {
String userCode = "lisen";
ShiroUser shiroUser = shiroService.findUserByCode(userCode);
return shiroUser;
}
@RequestMapping("/login")
public String login(@RequestParam("userCode") String userCode, @RequestParam("password") String password) {
// password = new SimpleHash(AuthenConst.HASH_ALGORITHM_NAME,password,AuthenConst.SALT,AuthenConst.HASH_INTERACTIONS).toHex();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userCode, password);
Subject subject = SecurityUtils.getSubject();
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
} catch (UnknownAccountException e) {
logger.error("用户名不存在!", e);
return "用户名不存在!";
} catch (AuthenticationException e) {
logger.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (AuthorizationException e) {
logger.error("没有权限!", e);
return "没有权限";
}
return "login success";
}
@RequestMapping("/index")
public String index(String userCode, String password) {
return "index";
}
@RequiresPermissions("permission")
@RequestMapping("/permission")
public String permission() {
return "permission";
}
@RequiresPermissions("dept:add")
@RequestMapping("/deptadd")
public String deptAdd() {
return "dept:add";
}
@RequestMapping("/nopermission")
public String noPermission() {
return "noPermission";
}
}
表数据
因为我们这里还没设计授权的界面,所以临时在数据库插入了测试数据
Shiro_User
INSERT INTO `shiro`.`Shiro_User`(`id`, `userCode`, `userName`, `password`) VALUES (1, 'lisen', '李森', 'f5e617c6615d53ae33b9d80a2087e264');
Shiro_Role
INSERT INTO `shiro`.`Shiro_Role`(`id`, `roleCode`, `roleName`) VALUES (1, 'admin', '管理员');
Shiro_Permission
INSERT INTO `shiro`.`Shiro_Permission`(`id`, `permissionCode`, `permissionName`) VALUES (1, 'dept:add', '部门-增加');
Shiro_User_Role
INSERT INTO `shiro`.`Shiro_User_Role`(`id`, `userId`, `roleId`) VALUES (1, 1, 1);
Shiro_Role_Permission
INSERT INTO `shiro`.`Shiro_Role_Permission`(`id`, `roleId`, `permissionId`) VALUES (1, 1, 1);
谢谢分享