最近看了下shiro这个框架,感觉使用还蛮方便的,话不多说直接上代码。
码云项目地址 : Springboot + shiro + redis + jwt + jpa
新建一个Springboot项目
1.pom.xml加入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
<exclusions>
<exclusion>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.61</version>
</dependency>
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
2. application.yml配置
server:
#端口号
port: 9002
spring:
# 数据源设置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/shirotest?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update
redis:
password:
timeout: 2000s
3. 实体类及repository
用户表
@Entity
@Data
@Table(name = "t_user")
@FieldDefaults(level = AccessLevel.PRIVATE)
public class UserEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String userName;
String password;
@Transient
String token;
}
权限表
@Data
@Entity
@Table(name="t_role")
public class RoleEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
String roleName;
}
用户权限中间表
@Entity
@Data
@Table(name="t_permission")
public class PermissionEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
Long userId;
Long roleId;
}
Repository
public interface UserRepository extends JpaRepository<UserEntity,Long> {
UserEntity findFirstByUserName(String userName);
}
public interface PermissionRepsitory extends JpaRepository<PermissionEntity,Long> {
List<PermissionEntity> findByUserId(Long userId);
}
4. 配置类
redis配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
/*
设置这些是 当redis的value设置为Jackson2JsonRedisSerializer,导致shiro反序列化时出错
直接使用JdkSerializationRedisSerializer不会出错,但是在RedisDesktopManager中,无法查看保存的数据
*/
ObjectMapper om = new ObjectMapper();
//在反序列化时忽略在JSON字符串中存在,而在Java中不存在的属性
om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
我们使用的是JwtToken来代替sessionId,所以创建了AuthToken类
AuthToken
public class AuthToken implements AuthenticationToken {
private String token;
public AuthToken() {
}
public AuthToken(String token) {
this.token = token;
}
public String getToken() {
return token;
}
@Override
public Object getPrincipal() {
return token;
}
@Override
public Object getCredentials() {
return token;
}
}
AuthRealm 是自定义用户登陆认证、权限设置
- 用户认证:初登陆外其余的请求在header头中必须带token这个标志,我们根据token标志到redis判断。我这里做了单点、登出后的token无效、token验证
- 授权:在用户认证成功后可以给该用户授予业务权限
@Slf4j
@Component
public class AuthRealm extends AuthorizingRealm {
@Autowired
private PermissionRepsitory permissionRepsitory;
//必须重写,不然会报错
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof AuthToken;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
log.debug("开始执行授权操作.......");
System.out.println("调用了授权方法");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//如果身份认证的时候没有传入User对象,这里只能取到userName
//也就是SimpleAuthenticationInfo构造的时候第一个参数传递需要User对象
UserEntity user = (UserEntity) principalCollection.getPrimaryPrincipal();
Long userId = user.getId();
List<PermissionEntity> list = permissionRepsitory.findByUserId(userId);
if(!list.isEmpty()){
list.forEach(o ->{
authorizationInfo.addStringPermission(o.getRoleId().toString());
});
}
return authorizationInfo;
}
//验证用户
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
log.info("验证开始。。。");
String token = (String) authenticationToken.getCredentials();