springboot整合shiro入门

   shiro作为安全框架,使用还是比较多,而且相对来说,比较容易上手。下面将使用springboot来整合shiro,实现的功能如下:

(1)实现访问控制,未登录时,只能访问登录接口

(2)实现角色和权限的访问控制

示例代码,已放在了GitHub上:https://github.com/qiuxinfa/shiro-study

先看下目录结构:

 

1.maven依赖:

    <dependencies>
        <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.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>1.4.0</version>
        </dependency>
    </dependencies>

 

2.创建一个用户类User:

public class User {
    private String username;
    private String password;
    private Integer enable;  //账号是否锁定
    //省略set、get、toString方法
}

 

3.创建一个业务查询类,模拟数据存储和数据查询功能,因为这里没有连接数据库:

@Service
public class UserService {

    //用来存储用户信息的map
    private Map<String,User> userInfo = new HashMap<>(3);

    public UserService(){
        //用户信息
        userInfo.put("admin",new User("admin","123",1));
        userInfo.put("sam",new User("sam","123",1));
        userInfo.put("qxf",new User("qxf","123",0));
    }

    //根据用户名,查找用户,模拟数据库查询
    public User getUserByUsername(String username){
        return userInfo.get(username);
    }

    //获取所有的用户
    public Map<String,User> getAllUser(){
        return userInfo;
    }

}

这里为了简单起见,用一个map来存储用户信息,实际使用中,用户信息肯定是存储在数据库中的。

 

4.自定义身份认证和授权的realm,继承了AuthorizingRealm ,重写认证和授权方法:

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        User user = (User)SecurityUtils.getSubject().getPrincipal();  //获取当前登录的用户信息

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //这里模拟数据库,进行角色和权限的处理
        //这里只是简单模拟,角色使用是用户名,权限是包含用户名的路径
        simpleAuthorizationInfo.addRole(user.getUsername());
        simpleAuthorizationInfo.addStringPermission("/"+user.getUsername());

        return simpleAuthorizationInfo;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        //这里模拟数据库查询用户,根据用户名查询
        User dbUser = userService.getUserByUsername(username);
        if (dbUser == null){
            //账号不存在
            throw new UnknownAccountException();
        }
        if (dbUser.getEnable()==0){
            //账号被锁定
            throw new LockedAccountException();
        }
        return new SimpleAuthenticationInfo(dbUser, dbUser.getPassword(), getName());
    }
}

 

5.配置shiro

/**
 *shiro配置
 */
@Configuration
public class ShiroConfig {
    @Bean
    MyRealm myRealm() {
        //返回自定义的身份认证和授权realm
        return new MyRealm();
    }

    @Bean
    SecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        //设置登录的url
        bean.setLoginUrl("/login");
        Map<String, String> map = new LinkedHashMap<>();
        //anon表示,匿名访问,不需要认证就可以访问的接口
        map.put("/doLogin", "anon");
        //authc表示需要身份认证后才可以访问
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

 //下面配置的3个bean,主要是为了Shiro的注解(如@RequiresRoles,@RequiresPermissions)生效
    @Bean
    public  LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

  
    @Bean
    public  DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

这里需要注意一下,如果想要使用shiro的注解生效,需要这3个bean:

(1)LifecycleBeanPostProcessor 

(2)DefaultAdvisorAutoProxyCreator

(3)AuthorizationAttributeSourceAdvisor

 

6.定义访问接口:

@Controller
public class ShiroController {

    @GetMapping("/login")
    public String login(){
        return "login.html";
    }

    @PostMapping("/doLogin")
    @ResponseBody
    public String doLogin(String username,String password){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);
            return "登录成功";
        }catch (UnknownAccountException|IncorrectCredentialsException e){
            e.printStackTrace();
            return "账号或密码错误";
        }catch (LockedAccountException e){
            e.printStackTrace();
            return "账号已被锁定,请联系管理员";
        }catch (AuthenticationException e){
            e.printStackTrace();
            return "未知异常,请联系管理员";
        }

    }

    @GetMapping("/hello")
    @ResponseBody
    public String hello(){
        return "hello:"+SecurityUtils.getSubject().getPrincipal();
    }


}

这里定义了3个接口:

(1)login,处理get请求,匿名可访问,会跳转到login.html登录页面

(2)doLogin,处理post请求,实现真正的登录功能

(3)hello,获取自身信息,认证后才可以访问

 

7.登录页面,写得很简陋,就是一个简单HTML的form表单提交:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

   <form action="/doLogin" method="post">
       用户名:<input type="text" name="username" />
       <br/>
       密  码:<input type="password" name="password"/>
       <br/>
       <input type="submit" value="登录"/>
   </form>

</body>
</html>

 

8.测试

(1)验证这个功能:未登录时,只能访问登录接口

写个启动类,启动项目后,在浏览器输入http://localhost:8080/hello,则会跳转到登录页面,因为在UserService里面添加了3个用户:

        //用户信息
        userInfo.put("admin",new User("admin","123",1));
        userInfo.put("sam",new User("sam","123",1));
        userInfo.put("qxf",new User("qxf","123",0));

所以,当我们使用admin或者sam登录时(密码都是123),就可以登录成功(qxf账号是被锁定了),登录成功之后,再访问hello就可以看到自己的信息了。

 

(2)验证角色和权限访问控制:

    在MyRealm的授权方法中,我是用 用户名作为角色添加了,用 /+用户名作为权限,真实项目肯定不是这样的,一般会配置在数据库中,这里只是为了简单起见:

        //这里模拟数据库,进行角色和权限的处理
        //这里只是简单模拟,角色使用是用户名,权限是包含用户名的路径
        simpleAuthorizationInfo.addRole(user.getUsername());
        simpleAuthorizationInfo.addStringPermission("/"+user.getUsername());

添加只有admin可以访问的接口:

@RestController
@RequestMapping("/admin")
public class AdminController {

    @Autowired
    private UserService userService;

    @GetMapping("/allUser")
    @RequiresRoles("admin")    //表示需要有[admin]这个角色才可以访问
    @RequiresPermissions("/admin") //表示需要有 /admin 权限才可以访问
    public Collection<User>  allUser(){
        List<User> list = new ArrayList<>();
        Map<String,User> userMap = userService.getAllUser();
        return userMap.values();
    }
}

再写一个全局异常处理,用以捕获没有权限访问的异常:

/**
 * @Auther: qiuxinfa
 * @Date: 2020/4/2
 * @Description: 定义全局异常处理
 */
@RestControllerAdvice
public class MyGlobalException {

    //表示只捕获AuthorizationException类及其子类异常
    @ExceptionHandler(AuthorizationException.class)
    public String handlerAuthorizationException(AuthorizationException e){
        e.printStackTrace();
        return "你没有权限访问";
    }
}

现在,可以启动项目了,如果用sam登录后,访问http://localhost:8080/admin/allUser,那么就会返回如下信息:

你没有权限访问

后台也会打印出,需要角色admin才可以访问,其实还需要/admin权限才可以访问

如果用admin登录后,访问http://localhost:8080/admin/allUser,则会返回所有的用户信息:

[{"username":"admin","password":"123","enable":1},{"username":"sam","password":"123","enable":1},{"username":"qxf","password":"123","enable":0}]

这样就实现了角色的访问控制,权限也是一样的道理,为了简单起见,demo中尽量返回的是json,只有一个登录页面。

其他注意点:

        授权不是认证之后就立马执行的,而是当你访问某个资源需要权限时,才会执行授权的方法,比如访问添加了

@RequiresRoles,RequiresPermissions注解的时候,才会触发进入到我们自定义realm的授权方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值