Spring Boot整合Shiro

2019/08/20 23:59
阅读数 35

作者:fzsyw

https://www.cnblogs.com/fzsyw/p/11373776.html


一,概述

4A(认证Authentication、授权Authorization、账号Account、审计Audit)是现代任何IT系统中很基础但非常重要的部分,无论是传统管理信息系统还是互联网项目,出于保护业务数据和应用自身的安全,都会设计自己的登录和资源授权策略。最近项目中需要登录和权限相关的功能,项目为spring-boot工程,现在流行的权限验证框架有shiro和spring-security,shiro相对spring-security来说学习难度要低一点,也是比较成熟的产品,因此选择shiro作为项目的权限验证框架。


二,步骤

2.1 添加依赖

spring boot的版本为2.1.7.RELEASE。如果大量依赖spring的项目,可以用https://start.spring.io/

patchca是验证码部分

<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.1.7.RELEASE</version>  </parent>


shiro-spring是用的最新的版本。patchca是用于验证码。

<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.1</version> </dependency>
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
<dependency> <groupId>com.github.bingoohuang</groupId> <artifactId>patchca</artifactId> <version>0.0.1</version> </dependency> </dependencies>


2.2 配置SecurityManager

在spring boot项目中去掉了复杂的各种xml配置,改为在Java文件中配置各种bean

@Bean(name = "securityManager")public org.apache.shiro.mgt.SecurityManager defaultWebSecurityManager(@Autowired UserRealm      userRealm) {    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();    // 关联realm    securityManager.setRealm(userRealm);    securityManager.setRememberMeManager(rememberMeManager());    return securityManager;}


2.3 配置ShiroFilterFactoryBean

可以添加Filter,以及各种资源的权限类型anon、authc、user、perms、role。ShiroFilterFactoryBean(该类实现了FactoryBean接口,在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式 我们可以在getObject()方法中灵活配置和扩展)

/** * 创建ShiroFilterFactoryBean shiro过滤bean */@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(@Autowired org.apache.shiro.mgt.SecurityManager securityManager) {    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();    shiroFilterFactoryBean.setSecurityManager(securityManager);
/** * anon: 无需认证(登录)可以访问 * authc: 必须认证才可以访问 * user: 如果使用rememberMe功能可以直接访问 * perms: 该资源必须得到资源权限才可以访问 * role: 该资源必须得到角色权限才可以访问 */ Map<String, String> filerMap = new LinkedHashMap<>(); // 顺序的map filerMap.put("/login", "anon"); filerMap.put("/validCode", "anon"); filerMap.put("/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/user/login.html"); shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth"); shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filerMap); return shiroFilterFactoryBean;}

2.4 创建和配置Realm


public class UserRealm extends AuthorizingRealm {
@Autowired private UserService userService; /** * 执行授权逻辑 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("执行授权逻辑1"); //给资源进行授权,这里暂时写死,实际需要从数据库中获取当前用户的资源权限 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.addStringPermission("user:add"); info.addStringPermission("user:update"); return info; }
/** * 执行认证逻辑 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { ValidCodeUserPassWordToken token = (ValidCodeUserPassWordToken)authenticationToken; String validCode = token.getValidCode(); if(StringUtils.isEmpty(validCode)) { throw new AuthenticationException("未输入验证码"); } //校验码部分 Subject subject = SecurityUtils.getSubject(); ValidationCode oldValidCode = (ValidationCode); subject.getSession().getAttribute("VALIDCODE"); subject.getSession().removeAttribute("VALIDCODE"); if(oldValidCode.isExpired()) { throw new AuthenticationException("验证码已过期"); } if(!oldValidCode.valid(validCode)) { throw new AuthenticationException("验证码输入错误"); } //实际需要根据账号,查询当前用户信息 User user = new User(); user.setId(123); user.setName("xs"); user.setPassword("123"); ByteSource salt= ByteSource.Util.bytes(user.getId().toString()); Object password = new SimpleHash("MD5", user.getPassword(), salt, 2); return new SimpleAuthenticationInfo( user, "297254e9bfe0b8f39c682eda30bb9be0", //密码 salt, getName() ); } }

2.5 配置UserRealm为Bean

 @Bean    public UserRealm userRealm() {        UserRealm myShiroRealm = new UserRealm();        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
myShiroRealm.setCachingEnabled(true); //启用身份验证缓存,即缓存AuthenticationInfo信息,默认false myShiroRealm.setAuthenticationCachingEnabled(false); //缓存AuthenticationInfo信息的缓存名称 myShiroRealm.setAuthenticationCacheName("authenticationCache"); //启用授权缓存,即缓存AuthorizationInfo信息,默认false myShiroRealm.setAuthorizationCachingEnabled(true); //缓存AuthorizationInfo信息的缓存名称 myShiroRealm.setAuthorizationCacheName("authorizationCache"); return myShiroRealm; } /** * 密码加密 */ private HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("MD5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 return hashedCredentialsMatcher; }

2.6 权限注解

@RequestMapping("/add")@RequiresPermissions("user:add")public String add() {    return "user/add";}


三,校验码


/** * 校验码 * @author Administrator * */public class ValidationCode {        public final static String VALID_CODE_NAME = "VALIDCODE";        private String code;        private Date createTime;        private int expireMillisecond = 6000;        private ValidationCode(String code) {        this.code = code;        this.createTime = new Date();    }        public static ValidationCode create(String code) {        return new ValidationCode(code);    }        /**     * 是否过期     */    public boolean isExpired() {        Long between_Millisecond  =  new Date().getTime()-createTime.getTime();        return between_Millisecond.intValue() > expireMillisecond;    }        /**     * 与客户端code比较是否一致     */    public boolean valid(String newCode) {        return this.code.equalsIgnoreCase(newCode);    }}检验码生成类

检验码生成类

@Controller@RequestMappingpublic class ValidationCodeController {    @RequestMapping("/validCode")    public void captcha(HttpServletRequest request, HttpServletResponse response,            @RequestParam(name = "w", defaultValue = "90") Integer width,            @RequestParam(name = "h", defaultValue = "38") Integer height,            @RequestParam(name = "n", defaultValue = "4") Integer number) throws IOException {        ConfigurableCaptchaService configurableCaptchaService = new ConfigurableCaptchaService();        configurableCaptchaService.setColorFactory(new SingleColorFactory(new Color(25, 60, 170)));        configurableCaptchaService                .setFilterFactory(new CurvesRippleFilterFactory(configurableCaptchaService.getColorFactory()));        RandomFontFactory randomFontFactory = new RandomFontFactory();        randomFontFactory.setMinSize(30);        randomFontFactory.setMaxSize(30);                RandomWordFactory randomWordFactory = new RandomWordFactory();        randomWordFactory.setMinLength(number);        randomWordFactory.setMaxLength(number);                configurableCaptchaService.setWordFactory(randomWordFactory);        configurableCaptchaService.setFontFactory(randomFontFactory);        configurableCaptchaService.setHeight(height);        configurableCaptchaService.setWidth(width);                response.setContentType("image/png");        response.setHeader("Cache-Control", "no-cache, no-store");        response.setHeader("Pragma", "no-cache");        long time = System.currentTimeMillis();        response.setDateHeader("Last-Modified", time);        response.setDateHeader("Date", time);        response.setDateHeader("Expires", time);
// 将VALIDCODE放入Session中 ServletOutputStream stream = null; try { HttpSession session = request.getSession(); stream = response.getOutputStream(); String validate_code = EncoderHelper.getChallangeAndWriteImage(configurableCaptchaService, "png", stream); session.setAttribute(ValidationCode.VALID_CODE_NAME, ValidationCode.create(validate_code)); stream.flush(); } finally { if (stream != null) { stream.close(); } } }}

四,统一异常处理

/** * 统一异常处理 */@RestController@ControllerAdvicepublic class ControllerExceptionHandler {    private Logger logger = LoggerFactory.getLogger(getClass());
@ExceptionHandler(DataAccessException.class) public Object handleDuplicateKeyException(DataAccessException e){ logger.error(e.getMessage(), e); return ResultUtil.error("数据库中已存在该记录"); }
@ExceptionHandler(AuthorizationException.class) public Object handleAuthorizationException(AuthorizationException e){ logger.error(e.getMessage(), e); return ResultUtil.error("没有权限,请联系管理员授权"); }
@ExceptionHandler(Exception.class) public Object handleException(Exception e){ logger.error(e.getMessage(), e); return ResultUtil.error(e.getMessage()); } @ExceptionHandler(IncorrectCredentialsException.class) public Object handleException(IncorrectCredentialsException e){ logger.error(e.getMessage(), e); return ResultUtil.error("用户名或者密码不对"); } @ExceptionHandler(UnknownAccountException.class) public Object handleException(UnknownAccountException e){ logger.error(e.getMessage(), e); return ResultUtil.error("请输入正确的账户"); }}


五,总结

目前搭建的项目,还没有从数据库获取数据,登录和权限获取的数据目前都是写死的。但是基本架子已经搭建好了,只需要在UserRealm中注入UserService类,提供数据库获取数据的服务即可。还有基于注解权限的方式需要注入LifecycleBeanPostProcessor和DefaultAdvisorAutoProxyCreator,并且DefaultAdvisorAutoProxyCreator.setProxyTargetClass(true)



本文分享自微信公众号 - java1234(gh_27ed55ecb177)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部