shiro

一、准备

1、pom

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</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>
		</dependency>

		<!--shiro-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.3.2</version>
		</dependency>

		<dependency>
			<groupId>redis.clients</groupId>
			<artifactId>jedis</artifactId>
			<version>2.8.2</version>
		</dependency>

		<!--导入html依赖-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<!--热部署-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

	</dependencies>

2、application.yml

spring:
  datasource:
    url: jdbc:mysql://ip:port/shiro3?characterEncoding=utf-8&useSSL=true
    driver-class-name: com.mysql.jdbc.Driver
    username: xxx
    password: xxx
    
    
   --自动创建表格 
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update 


  #thymeleaf模版前缀:也可以不设置,以下是默认的
  thymeleaf:
    prefix: classpath:/templates/ 
    cache: true
    mode: HTML5
    encoding: UTF-8
    servlet:
      content-type: text/html
      
  
  redis:
    database: 10
	host: http://127.0.0.0
    port: 6379
    timeout: 0
    
  aop:
    proxy-target-class: true

server:
  port: 8081

二、登录认证

1、登录页面

PageController

@Controller
@RequestMapping("/page")
public class PageController {
    /**
     * 这里不要加@ResponseBody,也就是不要以json方式返回,
     * 因为你要重定向到/templates/下的login.html页面
     * @return
     */
    @RequestMapping("/login")
    public String login(){
        return "login";
    }

html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    记住我:<input type="checkbox" name="rememberMe"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

启动项目,请求接口,如下图
在这里插入图片描述
UserController

@Controller
@RequestMapping("/usr")
public class UserController {
    @PostMapping("/login")
    @ResponseBody
    public String login(User user){
        Subject subject = SecurityUtils.getSubject();
        //用来接收来自前端的信息,比如username,password,host,rememeberme等
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return e.getMessage();
        }
        return "success login";
    }
}

ShiroConfig

@Configuration
public class ShiroConfig {
    //过滤器
    @Bean
    public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager); // 必须设置 SecurityManager

        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        //不会拦截的url
        filterChainDefinitionMap.put("/page/login","anon"); --login.html
        filterChainDefinitionMap.put("/usr/login","anon");  --登录url
        //所有url都必须有user权限才可以访问 一般讲放在最后面
        filterChainDefinitionMap.put("/**", "user");
        //重定向链接
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorizied");
        shiroFilterFactoryBean.setSuccessUrl("/");

        //设置拦截器
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    @Bean(name = "securityManager")
    public SecurityManager securityManager(@Qualifier("customRealm")CustomRealm customRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm);
        return securityManager;
    }

    @Bean(name = "customRealm")
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();
        return customRealm;
    }
}

CustomRealm

public class CustomRealm extends AuthorizingRealm{
    @Autowired
    private UserService userService;
    @Autowired
    private RoleService roleService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        User user = (User)principal.getPrimaryPrincipal();
        Long idd = Long.valueOf(user.getId());
        Set<String> roles = roleService.getRolesById(idd);
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        User user = userService.getUserByUsername(token.getUsername());

        SimpleAuthenticationInfo simpleAuthenticationInfo = null;
        if (user != null) {
            try {
                simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return simpleAuthenticationInfo;
        } else {
            return null;
        }
    }
}

domain,service,repository见我的github

2、加密

ShiroConfig

    @Bean  //配置HashedCredentialsMatcher
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashIterations(1);  //配置加密次数
        matcher.setHashAlgorithmName("md5");  //加密方式
        return matcher;
    }
    @Bean(name = "customRealm")
    public CustomRealm customRealm(HashedCredentialsMatcher matcher){
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(matcher); //为自定义realm设置加密方式
        return customRealm;
    }
    @Bean(name = "securityManager")
    public SecurityManager securityManager(@Qualifier("customRealm")CustomRealm customRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm); //为securityManager配置realm
        return securityManager;
    }

CustomRealm 实现AuthorizingRealm,重点看doGetAuthenticationInfo方法

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        User user = userService.getUserByUsername(token.getUsername());

        SimpleAuthenticationInfo simpleAuthenticationInfo = null;
        if (user != null) {
            try {
                simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), getName());
                simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getUsername())); //设置加盐。一般为随机数字,这里求方便,用用户名做盐值
            } catch (Exception e) {
                e.printStackTrace();
            }
            return simpleAuthenticationInfo;
        } else {
            return null;
        }
    }

用main方法生成一个盐值

    public static void main(String[] args) {
        Md5Hash admin = new Md5Hash("123", "gxd"); //“123”表示密码;"gxd"表示盐值,一遍用随机数,这里求方便,用用户名代替
        System.out.println(admin);
    }
进入到Md5Hash的构造方法
   public Md5Hash(Object source, Object salt) {
        super("MD5", source, salt);
    }
   

3、sessionManager

1、配置
pom.xml

<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>2.8.2</version>
</dependency>

application.yml 配置redis数据源

redis:
  database: 10
  port: 6379
  timeout: 0
  host: http://127.0.0.0

2、编写SessionDao。对session进行CRUD

@Component
public class SessionDao extends AbstractSessionDAO {
    @Resource
    private JedisUtil jedisUtil;
    private final String SHIRO_SESSION_PREFIX = "shiro-session";


    private byte[] getKey(String key) {
        return (SHIRO_SESSION_PREFIX + key).getBytes();
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        saveSession(session);
        return sessionId;
    }

    private void saveSession(Session session) {
        if (session != null && session.getId() != null) {
            byte[] key = getKey(session.getId().toString());
            byte[] value = SerializationUtils.serialize(session);
            System.out.println("key:"+key+"value:"+value);
            jedisUtil.set(key, value);
            jedisUtil.expire(key, 600);
        }
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            return null;
        }
        byte[] key = getKey(sessionId.toString());
        byte[] value = jedisUtil.get(key);
        return (Session) SerializationUtils.deserialize(value);
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        saveSession(session);
    }

    @Override
    public void delete(Session session) {
        if (session == null && session.getId() == null) {
            return;
        }
        byte[] key = getKey(session.getId().toString());
        jedisUtil.del(key);
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<byte[]> keys = jedisUtil.keys(SHIRO_SESSION_PREFIX);
        HashSet<Session> sessions = new HashSet<>();
        if (CollectionUtils.isEmpty(keys)) {
            return sessions;
        }
        for (byte[] key : keys) {
            Session session = (Session) SerializationUtils.deserialize(jedisUtil.get(key));
            sessions.add(session);
        }
        return sessions;
    }
}

3 、编写自定义SessionManager。如果采用shiro自带的shiro将会多次访问redis,redis的压力很大,所以,在自定义sessionManager中,将session存入request中,避免了此类问题。

public class CustomSessionManage extends DefaultWebSessionManager {

    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);
        ServletRequest request = null;
        if (sessionKey instanceof WebSessionKey) {
            request = ((WebSessionKey) sessionKey).getServletRequest();
        }
        if (request != null && sessionId != null) {
            Session session = (Session) request.getAttribute(sessionId.toString());
            if (session != null) {
                return session;
            }
        }
        Session session = super.retrieveSession(sessionKey);
        if (request != null && sessionId != null) {
            request.setAttribute(sessionId.toString(), session);
        }
        return session;
    }
}

4、 shiroConfig
向spring中注入jedis。

@Bean
public JedisPool getJedisPool() {
    JedisPoolConfig config = new JedisPoolConfig(); //config里面可以配置很多信息,大家可以根据需要添加,这里只用最简单的
    JedisPool pool = new JedisPool(config,"47.98.149.58",6379,200,"gxdredis!"); --可设置密码
    return pool;
}

//如果你是本地测试,可以采用最简单的配置
 @Bean 
    public JedisPool jedisPool(){
        JedisPool jedisPool = new JedisPool();
        return jedisPool;
    }

向spring中注入sessionManager,可以使用自定义sessionManager,并往sessionManager中注入sessionDao

@Bean(name = "sessionManager")
public SessionManager sessionManager(SessionDao sessionDao){
    CustomSessionManage customSessionManage = new CustomSessionManage();
    customSessionManage.setSessionDAO(sessionDao);
    return customSessionManage;
}

将sessionManager注入到securityManager中

@Bean(name = "securityManager")
public SecurityManager securityManager(@Qualifier("sessionManager") SessionManager sessionManager){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setSessionManager(sessionManager);
    return securityManager;
}

5、工具类jedisUtil

@Component
public class JedisUtil {
    @Resource
    public JedisPool jedisPool;

    private Jedis getResource(){
        return jedisPool.getResource();
    }

    public byte[] set(byte[] key, byte[] value) {
        Jedis jedis = getResource();
        try {
            jedis.set(key,value);
            return value;
        } finally {
            jedis.close();
        }
    }

    public void expire(byte[] key, int i) {
        Jedis jedis = getResource();
        try {
            jedis.expire(key,i);
        } finally {
            jedis.close();
        }
    }

    public byte[] get(byte[] key) {
        Jedis resource = getResource();
        try {
            return resource.get(key);
        } finally {
            resource.close();
        }
    }

    public void del(byte[] key) {
        Jedis resource = getResource();
        try {
            resource.del(key);
        } finally {
            resource.close();
        }
    }
    public Set<byte[]> keys(String shiro_session_prefix) {
        Jedis resource = getResource();
        try {
            return resource.keys((shiro_session_prefix+"*").getBytes());
        } finally {
            resource.close();
        }
    }
}

打开redis-cli,查看session

127.0.0.1:6379> keys *
1) "shiro-sessioncfa3d0a5-6010-423c-8979-dbdf953685c8"
2) "shiro-sessioncafcd0a9-f4cd-41aa-9d39-e2a9fa543c03"
3) "name"

4 、CacheManager

RedisCache

@Component
public class RedisCache<K,V> implements Cache<K,V> {
    private final String CACHE_PREFIX = "shiro-cache";
    @Autowired
    private JedisUtil jedisUtil;
    @Override
    public V get(K k) throws CacheException {
        System.out.println("从Redis中获取权限数据");
        byte[] value = jedisUtil.get(getKey(k));
        if (value != null) {
            return (V) SerializationUtils.deserialize(value);
        }
        return null;
    }

    @Override
    public V put(K k, V v) throws CacheException {
        byte[] key = getKey(k);
        byte[] value = SerializationUtils.serialize(v);
        jedisUtil.set(key, value);
        jedisUtil.expire(key, 600);
        return v;
    }
    @Override
    public V remove(K k) throws CacheException {
        byte[] key = getKey(k);
        byte[] value = SerializationUtils.serialize(key);
        jedisUtil.del(key);
        if (value != null) {
            return (V) SerializationUtils.deserialize(value);
        }
        return null;
    }
    @Override
    public void clear() throws CacheException {

    }
    @Override
    public int size() {
        return 0;
    }
    @Override
    public Set<K> keys() {
        return null;
    }
    @Override
    public Collection<V> values() {
        return null;
    }
    private byte[] getKey(K k) {
        if (k instanceof String) {
            return (CACHE_PREFIX + k).getBytes();
        }
        return SerializationUtils.serialize(k);
    }
}

RedisCacheManager

public class RedisCacheManager implements CacheManager {
    @Resource
    private RedisCache redisCache;
    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        return redisCache;
    }
}

shiroConfig

    @Bean(name = "redisCacheManager")
    public RedisCacheManager redisCacheManager(){
        return new RedisCacheManager();
    }
    
	@Bean(name = "securityManager")
	public SecurityManager securityManager(@Qualifier("redisCacheManager") RedisCacheManager redisCacheManager){
   		 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    	securityManager.setCacheManager(redisCacheManager);
    return securityManager;
}

controller测试
正常情况下,首次查询,会去数据库,第二次开始, 直接从redis缓存中获取

		if (subject.hasRole("user")){
            return "有user权限";
        }else {
            return "没有权限";
        }

5、RememberMe

shiroConfig

@Bean //为SimpleCookie设值,并获取SimpleCookie 
public SimpleCookie rememberMeCookie() {
    //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
    SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    //<!-- 记住我cookie生效时间30天 ,单位秒;-->
    simpleCookie.setMaxAge(259200);
    return simpleCookie;
}
@Bean //为CookieRememberMeManager设置SimpleCookie 
public CookieRememberMeManager rememberMeManager() {
    CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
    cookieRememberMeManager.setCookie(rememberMeCookie());
    return cookieRememberMeManager;
}

@Bean(name = "securityManager")
public SecurityManager securityManager(){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRememberMeManager(rememberMeManager());
    return securityManager;
}

controller

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
token.setRememberMe(user.isRememberMe());
subject.login(token);

user类

private boolean rememberMe;
public boolean isRememberMe() {
    return rememberMe;
}

public void setRememberMe(boolean rememberMe) {
    this.rememberMe = rememberMe;
}

前端

<form action="/user/login" method="post">
    用户名:<input type="text" name="username">

    密码:<input type="password" name="password">

    记住我:<input type="checkbox" name="rememberMe">

    <input type="submit" value="登录">
</form>

检验
在这里插入图片描述

6、springboot使用注解实现shiro的权限控制

1、shiroConfig

	@Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor
            = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    
	@Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

2、pom.xml中加

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

3、application配置

**如果是application.properties**
spring.aop.proxy-target-class=true

**如果是application.yml**
  aop:
    proxy-target-class: true

4、controller验证

    @ResponseBody
    @RequestMapping("/test1")
    public String test1(){
        return "test1";
    }

    @ResponseBody
    @RequiresPermissions("/test2")
    @RequestMapping("/test2")
    public String test(){
        return "test2";
    }

结果:test1方法可以访问,但是test2报错“Subject does not have permission ”错误
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值