<!-- 支持redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Spring Session 分布式事务 ,在登录时使用 -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- spring-session -->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
#配置连接redis的参数
spring:
redis:
cluster:
#配置redis集群节点
nodes: 92.168.10.140:8001,192.168.10.140:8002,192.168.10.140:8003,192.168.10.140:8004,192.168.10.140:8005,192.168.10.140:8006
session:
store-type: redis #存放session的存储方式
redis:
flush-mode: on_save #配置session刷新方式
timeout: 30m #设置session超时时间
这样redis的基本环境就搭建完成了。
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
Shiro 配置类-》ShiroConfig.java
–负责配置shiro的资源配置、安全管理器对象、和管理自定义的realm对象
@Configuration
public class ShiroConfig {
//创建shiroFilter(shiro拦截器对象--负责拦截所有请求)
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
//给Filter设置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统受限资源
//配置系统公共资源
Map<String,String> map=new HashMap<String,String>();
/*资源配置
map.put("/index","authc");//authc受限资源需要认证才能访问(“/**”)表示所有资源
map.put("/","anon");//anon设置公共资源
map.put("/**","authc");//存在‘/**’时未受限的资源必须写在上面,否则将被覆盖
//默认认证界面路径(认证失败会自动重定向返回到该路径界面)
*/
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
//创建安全管理对象
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//给安全管理器设置Realm
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
//创建自定义Realm
@Bean
public Realm getRealm(){
CustomerRealm customerRealm = new CustomerRealm();
//修改凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为Md5
credentialsMatcher.setHashAlgorithmName("MD5");
//设置散列次数
credentialsMatcher.setHashIterations(1024);
customerRealm.setCredentialsMatcher(credentialsMatcher);
//设置缓存管理器
customerRealm.setCacheManager(new RedisCacheManager());
//开启缓存
customerRealm.setCachingEnabled(true);
//开启认证缓存并指定缓存名称
customerRealm.setAuthenticationCachingEnabled(true);
customerRealm.setAuthenticationCacheName("AuthenticationCache");
//开启授权缓存并指定缓存名称
customerRealm.setAuthorizationCachingEnabled(true);
customerRealm.setAuthorizationCacheName("AuthorizationCache");
return customerRealm;
}
}
自定义Realm-》 CustomerRealm.java
用来处理用户的认证与授权
public class CustomerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//授权
//获取身份信息
// String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
// //根据身份信息查取数据库获取 权限 和 角色 信息
// if(primaryPrincipal.equals("xwm")){ //(模拟操作)
// SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
// simpleAuthorizationInfo.addRole("admin");//为用户添加角色
// simpleAuthorizationInfo.addStringPermission("admin:*:*");//admin角色下的所有资源的所有权限
// return simpleAuthorizationInfo; //返回对象
// }
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//认证
String principal = (String)authenticationToken.getPrincipal();//获取用户名
UserServiceImpl userService=(UserServiceImpl) ApplicationContextUtils.getContext("UserService");//获取容器里的Bean
Patient patient = userService.loginUser(principal);//通过姓名获取用户信息
if(patient!=null){//用户名,用户已加密的密码,盐,当前对象
System.out.println("-将patient对象存入session-");//会同时将将patient对象存入session中
return new SimpleAuthenticationInfo(patient,patient.getPatientpwd(),//密码
new CustomerByteSource(patient.getPsalt()),//盐
this.getName());//当前realm
}
return null;
}
}
用来为用户加密产生加密盐值的工具类
可以产生随机的字符串,可以加强加密的安全性。
public class SaltUtils {
public static String getSalt(int n){//快捷键:Ctrl+Shift+U==>可将选中的字母大写转小写
char[] chars = "ABCDEFJHIJKLMNOPQRSTUVWXYZabcdefjhijklmnopqrstuvwxyz0987654321!@#$%^&*():?><|~/}{][`".toCharArray();
StringBuilder str=new StringBuilder();
for (int i = 0; i < n; i++) {
char aChar = chars[new Random().nextInt(chars.length)];
str.append(aChar);
}
return str.toString();
}
public static void main(String[] args) {//盐值生成测试
String str=getSalt(8);
System.out.println(str);
Md5Hash md5Hash=new Md5Hash("123",str,1024);
System.out.println(md5Hash.toHex());
}
}
自定义盐值实现类(由于原本的盐实现是没有序列化的,在redis缓存过程中会出现序列化错误,所以需要自定义一个salt实现,实现序列化接口。)
//自定义salt实现 实现序列化接口
public class CustomerByteSource implements ByteSource, Serializable {
private byte[] bytes;
private String cachedHex;
private String cachedBase64;
public CustomerByteSource() {
}
public CustomerByteSource(byte[] bytes) {
this.bytes = bytes;
}
public CustomerByteSource(char[] chars) {
this.bytes = CodecSupport.toBytes(chars);
}
public CustomerByteSource(String string) {
this.bytes = CodecSupport.toBytes(string);
}
public CustomerByteSource(ByteSource source) {
this.bytes = source.getBytes();
}
public CustomerByteSource(File file) {
this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(file);
}
public CustomerByteSource(InputStream stream) {
this.bytes = (new CustomerByteSource.BytesHelper()).getBytes(stream);
}
public static boolean isCompatible(Object o) {
return o instanceof byte[] || o instanceof char[] || o instanceof String || o instanceof ByteSource || o instanceof File || o instanceof InputStream;
}
@Override
public byte[] getBytes() {
return this.bytes;
}
@Override
public boolean isEmpty() {
return this.bytes == null || this.bytes.length == 0;
}
@Override
public String toHex() {
if (this.cachedHex == null) {
this.cachedHex = Hex.encodeToString(this.getBytes());
}
return this.cachedHex;
}
@Override
public String toBase64() {
if (this.cachedBase64 == null) {
this.cachedBase64 = Base64.encodeToString(this.getBytes());
}
return this.cachedBase64;
}
@Override
public String toString() {
return this.toBase64();
}
@Override
public int hashCode() {
return this.bytes != null && this.bytes.length != 0 ? Arrays.hashCode(this.bytes) : 0;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (o instanceof ByteSource) {
ByteSource bs = (ByteSource) o;
return Arrays.equals(this.getBytes(), bs.getBytes());
} else {
return false;
}
}
private static final class BytesHelper extends CodecSupport {
private BytesHelper() {
}
public byte[] getBytes(File file) {
return this.toBytes(file);
}
public byte[] getBytes(InputStream stream) {
return this.toBytes(stream);
}
}
}
通过实现CacheManager接口为shiro配置redis缓存管理器。
public class RedisCacheManager implements CacheManager {
@Override
public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
System.out.println("缓存名称: "+cacheName);
return new RedisCache<K,V>(cacheName);
}
}
自定义redis缓存实现RedisCache类,内部都是操作redis数据库的方法。
//存储键值对不应该使用注解方式而应该从容器获取对象,因为这种方式只能存储值为字符串的键值对。
public class RedisCache<K,V> implements Cache<K,V> {
private String cacheName;
public RedisCache() {
}
public RedisCache(String cacheName) {
this.cacheName = cacheName;
}
@Override
public V get(K k) throws CacheException {
System.out.println("获取缓存:"+ k);
return (V) getRedisTemplate().opsForHash().get(this.cacheName,k.toString());
}
@Override
public V put(K k, V v) throws CacheException {
System.out.println("设置缓存key: "+k+" value:"+v);
getRedisTemplate().opsForHash().put(this.cacheName,k.toString(),v);
return null;
}
@Override
public V remove(K k) throws CacheException {
System.out.println("移除缓存key: "+k);
return (V) getRedisTemplate().opsForHash().delete(this.cacheName,k.toString());
}
@Override
public void clear() throws CacheException {
getRedisTemplate().delete(this.cacheName);
}
@Override
public int size() {
return getRedisTemplate().opsForHash().size(this.cacheName).intValue();
}
@Override
public Set<K> keys() {
return getRedisTemplate().opsForHash().keys(this.cacheName);
}
@Override
public Collection<V> values() {
return getRedisTemplate().opsForHash().values(this.cacheName);
}
private RedisTemplate getRedisTemplate(){//获取redis操作对象 RedisTemplate
//通过工具类ApplicationContextUtils指定Bean名来获取容器中指定的Bean对象
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getContext("redisTemplate");
return redisTemplate;
}
}
用来从容器里获取Bean对象的工具类ApplicationContextUtils
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext context;
@Override//系统会自动调用该类经工厂传如该函数(由程序自动调用)
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context=applicationContext;
}
//根据bean名字获取工厂里指定的bean对象
public static Object getContext(String name){
Object bean = context.getBean(name);
return bean;
}
}
redis配置类 用来配置redis个类型的序列化方式(如果不配置该项,程序会出现反序列化失败的错误)
注:在分布式项目里,如果想要这种方式来实现session共享则必须两端即双方的redis缓存序列化方式都必须一样否则程序会出现序列化的异常错误。从而使程序无法正常运行。
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置value的序列化规则和 key的序列化规则
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
在分布式项目中,A模块内通过session.setAttribute("xxx",xxx)
将数据存入redis中,再通过ModelAndView view = new ModelAndView(new RedirectView("请求地址"))
来重定向到B模块,通过测试B模块的session.getAttribute("xxx")
来判断共享是否成功,如果有值则代表session共享成功。