shiro
配置文件:以.ini结尾的文件,存储权限相关信息,其实是学习是做测试数据用的,做项目的时候都是在数据库里面查询的
pom依赖
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version>
</dependency>
shiro.ini配置文件
[users]
xiaozhu=123
xiaoli=1234
认证
public class TestDemo {
public static void main(String[] args) {
//创建安全管理器
SecurityManager securityManager =new DefaultSecurityManager(new IniRealm("classpath:shiro.ini"));
//使用utils工具类,告诉工具类我们要使用的安全管理器
SecurityUtils.setSecurityManager(securityManager);
//用户
Subject subject = SecurityUtils.getSubject();
//创建token
UsernamePasswordToken xiaozhu = new UsernamePasswordToken("xiaozhu", "123");
try {
System.out.println(subject.isAuthenticated());
subject.login(xiaozhu);//登录
System.out.println(subject.isAuthenticated());
} catch (AuthenticationException e) {
e.printStackTrace();
}
}
}
如果用户名不对,会报UnknownAccountException异常
如果密码不对,会报IncorrectCredentialsException异常
所以在try,catch里面里可以根据异常类型判断出是用户名不对还是密码不对
通过断点一步一步走,发现用户名的认证在SimpleAccountRealm.doGetAuthenticationInfo方法中进行的,密码的认证是在AuthenticatingRealm.assertCredentialsMatch方法中进行的
自定义Realm
实现认证授权数据自定义,不从shiro.ini文件获取
继承AuthorizingRealm,实现doGetAuthorizationInfo和doGetAuthenticationInfo方法
/**
* 自定义Realm,实现认证授权数据从数据库获取
* @author 朱桂林 create 2021/2/2 15:35
*/
public class MyRealm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
if ("xiaozhu".equals(principal)) {
//后面可以从数据库获取
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123",getName());
return simpleAuthenticationInfo;
}
return null;
}
}
自定义realm做加密使用随机盐并且散列100次
public class MyMd5Realm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
/**
* 认证
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
if ("xiaozhu".equals(principal)) {
//654112e1c6bcfae709263e981b4ffe9c是使用shiro的md5加密出来的
//Md5Hash md5Hash = new Md5Hash("123","salt",100);
//System.out.println(md5Hash.toHex());
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"654112e1c6bcfae709263e981b4ffe9c", ByteSource.Util.bytes("salt"),getName());
return simpleAuthenticationInfo;
}
return null;
}
public class MyMd5RealmTestDemo {
public static void main(String[] args) {
MyMd5Realm singleRealm = new MyMd5Realm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName("md5");//使用md5做加密需要设置加密类型
credentialsMatcher.setHashIterations(100);//如果做散列的话,需要告诉匹配器散列次数
singleRealm.setCredentialsMatcher(credentialsMatcher);
SecurityManager securityManager = new DefaultSecurityManager(singleRealm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("xiaozhu", "123");
subject.login(token);
System.out.println(subject.isAuthenticated());
}
}
授权
public class MyMd5Realm extends AuthorizingRealm {
/**
* 授权
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println(primaryPrincipal);
Set<String> roles=new HashSet<>();
roles.add("admin");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(roles);
authorizationInfo.addStringPermission("user:*:01");
return authorizationInfo;
}
}
if(subject.isAuthenticated()){
//基于角色
System.out.println(subject.hasRole("admin"));//是否拥有某一个角色
System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user")));//是否拥有多个角色
System.out.println(Arrays.toString(subject.hasRoles(Arrays.asList("admin", "user"))));//是否拥有其中一个角色
System.out.println("====================");
//基于资源
System.out.println(subject.isPermitted("user:*:01"));
System.out.println(subject.isPermitted("user:*:02"));
System.out.println(subject.isPermitted("user:*:*"));
}
注意,只有subject在进行权限检查等情况才会去调用doGetAuthorizationInfo方法,并且检查一个角色或者资源权限都会调用doGetAuthorizationInfo方法一次
springboot整合shiro
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jsp-api</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
//自定义Realm
public class MySpringBootRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private UserroleDao userroleDao;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
User user = userService.findByUsername(primaryPrincipal);
Set<String> roles = userroleDao.findByUserid(user.getId()).stream().collect(Collectors.toSet());
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(roles);
return authorizationInfo;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
User user = userService.findByUsername(principal);
if (user != null) {
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),new MyByteSource(user.getSalt()),getName());
return simpleAuthenticationInfo;
}
return null;
}
}
//整合shiro相关的配置类
@Configuration
public class ShiroConfig {
//filter
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
HashMap<String, String> filterChainDefinitionMap = new HashMap<>();
//authc表示需要认证和授权
//anon 不用认证和授权,代表公共资源
filterChainDefinitionMap.put("/index.jsp","authc");
filterChainDefinitionMap.put("/regist.jsp","anon");
filterChainDefinitionMap.put("/**","authc");
filterChainDefinitionMap.put("/user/*","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
//默认就是/login.jsp
shiroFilterFactoryBean.setLoginUrl("/login.jsp");
return shiroFilterFactoryBean;
}
//安全管理器
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MySpringBootRealm realm){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(realm);
return defaultWebSecurityManager;
}
//realm
@Bean
public MySpringBootRealm realm(){
MySpringBootRealm mySpringBootRealm = new MySpringBootRealm();
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashIterations(100);
credentialsMatcher.setHashAlgorithmName("md5");
mySpringBootRealm.setCredentialsMatcher(credentialsMatcher);
//整合缓存,若没有整合,可注释以下代码
//mySpringBootRealm.setCacheManager(new EhCacheManager());//使用ehcache的manager
mySpringBootRealm.setCacheManager(new RedisCacheManager());//使用redisCacheManager
mySpringBootRealm.setCachingEnabled(true);//开启全局缓存
mySpringBootRealm.setAuthenticationCachingEnabled(true);//开启认证缓存
mySpringBootRealm.setAuthenticationCacheName("authenticationCache");
mySpringBootRealm.setAuthorizationCachingEnabled(true);//开启授权缓存
mySpringBootRealm.setAuthorizationCacheName("authorizationCache");
return mySpringBootRealm;
}
}
//UserController
@Controller
@RequestMapping("/user")
public class UserController {
@Value("${shiro.salt}")
private String salt;
@Value("${shiro.password.md5.number}")
private Integer number;
@Autowired
private UserService userService;
@RequestMapping("/login")
public String login(String username,String password){
Subject subject = SecurityUtils.getSubject();
try {
subject.login(new UsernamePasswordToken(username,password));
System.out.println("登录成功");
subject.hasRole("admin");
return "redirect:/index.jsp";
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误");
} catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误");
}
return "redirect:/login.jsp";
}
@RequestMapping("/loginout")
public String loginout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login.jsp";
}
@RequestMapping("/regist")
public String regist(String username,String password){
if(StringUtils.hasText(username)&&StringUtils.hasText(password)){
User user =new User();
user.setUsername(username);
String salt1 = Utils.getSalt();
user.setSalt(salt1);
Md5Hash md5Hash=new Md5Hash(password, salt1,number);
String password1 = md5Hash.toHex();
System.out.println(password1);
user.setPassword(password1);
userService.regist(user);
return "redirect:/login.jsp";
}
return "redirect:/regist.jsp";
}
@RequestMapping("/encache")
public String encache(){
Subject subject = SecurityUtils.getSubject();
subject.isAuthenticated();
subject.hasRole("admin");
return "redirect:/index.jsp";
}
}
server.servlet.context-path=/shiro
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
spring.datasource.url=jdbc:mysql://***:3306/shiro?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=***
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
shiro.salt=shiroSalt
shiro.password.md5.number=100
spring.jpa.show-sql=true
spring.redis.host=***
spring.redis.password=***
spring.redis.database=0
如果使用缓存要序列化时,加盐的SimpleByteSource类是无法序列化的,需要自行实现ByteSource接口,内容可全部复制,在实现Serializable接口
整合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("get-K:"+k);
RedisTemplate redisTemplate = (RedisTemplate) Utils.getBean("redisTemplate");
Object o = redisTemplate.opsForHash().get(cacheName,k.toString());
V value = (V) o;
return value;
}
@Override
public V put(K k, V v) throws CacheException {
System.out.println("put-K:"+k);
System.out.println("put-v:"+v);
RedisTemplate redisTemplate = (RedisTemplate) Utils.getBean("redisTemplate");
redisTemplate.opsForHash().put(cacheName,k.toString(),v);
return null;
}
@Override
public V remove(K k) throws CacheException {
RedisTemplate redisTemplate = (RedisTemplate) Utils.getBean("redisTemplate");
return (V) redisTemplate.opsForHash().delete(cacheName,k.toString());
}
@Override
public void clear() throws CacheException {
RedisTemplate redisTemplate = (RedisTemplate) Utils.getBean("redisTemplate");
redisTemplate.opsForHash().delete(cacheName);
}
@Override
public int size() {
RedisTemplate redisTemplate = (RedisTemplate) Utils.getBean("redisTemplate");
return redisTemplate.opsForHash().size(cacheName).intValue();
}
@Override
public Set<K> keys() {
RedisTemplate redisTemplate = (RedisTemplate) Utils.getBean("redisTemplate");
redisTemplate.opsForHash().keys(cacheName);
return null;
}
@Override
public Collection<V> values() {
RedisTemplate redisTemplate = (RedisTemplate) Utils.getBean("redisTemplate");
return redisTemplate.opsForHash().values(cacheName);
}
}
public class RedisCacheManager implements CacheManager {
/**
* @param s 认证或者授权缓存的名称
* @param <K>
* @param <V>
* @return
* @throws CacheException
*/
@Override
public <K, V> Cache<K, V> getCache(String s) throws CacheException {
RedisCache<K, V> kvRedisCache = new RedisCache<K, V>(s);
return kvRedisCache;
}
}
public class Utils {
private static ApplicationContext context;
public static String getSalt(){
return UUID.randomUUID().toString().substring(4,8);
}
//在启动类里面将ApplicationContext传递进来
public static void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context=applicationContext;
}
public static <T> T getBean(Class<T> clazz){
T bean = context.getBean(clazz);
return bean;
}
public static Object getBean(String name){
Object bean = context.getBean(name);
return bean;
}
}
记得在自定义的realm里面开启缓存,代码上面有
在整合redis的时候,最开始使用的时k.toString()作为键,而且没有map存储,然后总是报错,说是什么类型转换错误,我猜测是认证信息和权限信息都是使用的同一个key,后面我将缓存名称传递进来,使用缓存名称做hashmap,也就是把认证信息和权限信息分开存储,也就避免了无法区分信息的问题
实现cache时需要将Redistemplate注入进来,由于我们这个类不是被spring托管的,我们就写了一个工具类将Redistemplate注入进来,使用getBean方法的时候,这里不要使用通过class来查找bean,因为在spring里面有两个Redistemplate类型的bean,分别是redistemplate和stringRedistemplate
代码:https://download.csdn.net/download/Actor2/15048329