shiro应用

/ 工具和中间件 / 2 条评论 / 1172浏览

shiro在servlet应用

配置web.xml

    <!--同等于
    //加载配置文件,并获取工厂
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    //获取安全管理者实例
    SecurityManager sm = factory.getInstance();
    //将安全管理者放入全局对象
    SecurityUtils.setSecurityManager(sm);-->
    
    <listener>
        <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>shiroEnvironmentClass</param-name>
        <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默认先从/WEB-INF/shiro.ini,如果没有找classpath:shiro.ini -->
    </context-param>
    <context-param>
        <param-name>shiroConfigLocations</param-name>
        <param-value>classpath:shiro.ini</param-value>
    </context-param>
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

之前shiro基础中User,UserDao,DatabaseRealm

直接赋值过来使用即可

public class DatabaseRealm extends AuthorizingRealm {

    /**
     * 授权:已经认证过 进行授权操作
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //能进入到这里,表示账号登录成功了
        String username = (String) principalCollection.getPrimaryPrincipal();
        //通过dao获取角色和权限
        Set<String> roles = new UserDao().listRoles(username);
        Set<String> permissions = new UserDao().listPermissions(username);

        //授权对象
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        //把dao获取到的角色和权限放到用户中去
        s.setRoles(roles);
        s.setStringPermissions(permissions);
        return s;
    }

    /**
     * 认证:对用户进行认证校验操作
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户名和密码
        UsernamePasswordToken t = (UsernamePasswordToken) token;
        String username = token.getPrincipal().toString();
        String password = new String(t.getPassword());

        //获取数据库中的密码、盐
        User user = new UserDao().getUser(username);
        String passwordDb = user.getPassword();
        String salt = user.getSalt();

        //加密 加盐后的密码
        String encodedPassword = new SimpleHash("md5",password,salt,2).toString();

        //如果数据库中密码为空就是用户不存在,如果不同就是密码错误,此处统一抛出AuthenticationException,而不是抛出具体错误原因,免得给破解者提供帮助信息
        if(passwordDb == null || !passwordDb.equals(encodedPassword))
            throw new AuthenticationException();

        //认证信息里存放账号密码,getName是当前Realm的继承方法,通常返回当前类名:databaseRealm
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password,getName());
        return info;
    }
}

增加一个LoginServlet

@WebServlet(name = "loginServlet", urlPatterns = "/login")
public class LoginServlet extends HttpServlet {

    public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        //获取前台提交的数据
        String username = req.getParameter("name");
        String password = req.getParameter("password");
        //得到subject对象
        Subject subject = SecurityUtils.getSubject();
        //获取token
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try{
            //登录认证和授权
            subject.login(token);
            HttpSession session = req.getSession();
            session.setAttribute("subject", subject);
            res.sendRedirect("/shiro/index.jsp");
        }catch (AuthenticationException e){
            req.setAttribute("error", "验证失败");
            req.getRequestDispatcher("login.jsp").forward(req, res);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

配置shiro.ini

[main]
# 自己配置的Realm类路径
databaseRealm= io.imwj.realm.DatabaseRealm
securityManager.realms=$databaseRealm

#当访问需要验证的页面,但是又没有验证的情况下,跳转到login.jsp
authc.loginUrl=/login.jsp
#当访问需要角色的页面,但是又不拥有这个角色的情况下,跳转到noroles.jsp
roles.unauthorizedUrl=/noRoles.jsp
#当访问需要权限的页面,但是又不拥有这个权限的情况下,跳转到noperms.jsp
perms.unauthorizedUrl=/noPerms.jsp

#users,roles和perms都通过前面知识点的数据库配置了
[users]

#urls用来指定哪些资源需要什么对应的授权才能使用
[urls]
#doLogout地址就会进行退出行为
/doLogout=logout
#login.jsp,noroles.jsp,noperms.jsp 可以匿名访问
/login.jsp=anon
/noroles.jsp=anon
/noperms.jsp=anon

#查询所有产品,需要登录后才可以查看
/listProduct.jsp=authc
#删除商品不仅需要登录,而且要拥有 productManager 权限才可以操作
/deleteProduct.jsp=authc,roles[productManager]
#删除订单,不仅需要登录,而且要拥有 deleteOrder 权限才可以操作
/deleteOrder.jsp=authc,perms["deleteOrder"]

测试

前台书写一个登录的from表单,提交name和password到后台做登录校验即可

shiro在ssm中配置url权限

URLPathMatchingFilter拦截器

/**
 * @author langao_q
 * @create 2019-12-30 17:11
 * URL路径匹配过滤器
 * PathMatchingFilter:shiro内置过滤器
 * 1. 如果没登录就跳转到登录
 * 2. 如果当前访问路径没有在权限系统里维护,则允许访问
 * 3. 当前用户所拥有的权限如果不包含当前的访问地址,则跳转到/unauthorized,否则就允许访问
 */
public class URLPathMatchingFilter extends PathMatchingFilter {
    @Autowired
    PermissionService permissionService;

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        String requestURI = getPathWithinApplication(request);

        System.out.println("requestURI:" + requestURI);

        Subject subject = SecurityUtils.getSubject();
        //如果没有登录:就跳转到登录页面
        if(!subject.isAuthenticated()){
            WebUtils.issueRedirect(request, response, "/login");
            return false;
        }
        //看看这个路径是否在权限表里面,如果没有就放行
        boolean b = permissionService.needInterceptor(requestURI);
        if(!b){
            return true;
        }else {
            boolean hasPermission = false;
            String userName = subject.getPrincipal().toString();
            Set<String> permissionURLs = permissionService.listPermissionURLs(userName);
            for(String url : permissionURLs){
                //这里表示当前用户有这个权限
                if(url.equals(requestURI)){
                    hasPermission = true;
                    break;
                }
            }
            if(hasPermission)
                return true;
            else {
                UnavailableException ex = new UnavailableException("当前用户没有访问路径 " + requestURI + " 的权限");

                subject.getSession().setAttribute("ex", ex);
                WebUtils.issueRedirect(request, response, "/unauthorized");
                return false;
            }
        }
    }
}

springboot中使用shiro

认证和授权的JPARealm

public class JPARealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 授权:已经认证过 进行授权操作
     * 这里没有权限操作 所以无需授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
        return s;
    }

    /**
     * 认证:对用户进行认证校验操作
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获取用户名
        String username = token.getPrincipal().toString();

        //获取数据库中的密码
        User user = userService.getByName(username);
        String passwordInDB = user.getPassword();
        String salt = user.getSalt();

        //认证信息里存放账号密码,getName是当前Realm的继承方法,通常返回当前类名:databaseRealm
        //使用Shiro提供的 HashedCredentialsMatcher 帮我们做密码校验:
        //这样通过shiro.ini里配置的 HashedCredentialsMatcher 进行自动校验
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, passwordInDB, ByteSource.Util.bytes(salt), getName());
        return info;
    }
}

shiro配置类

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author langao_q
 * @create 2020-01-10 14:27
 * shiro配置类
 */
@Configuration
public class ShiroConfiguration {
    @Bean
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    @Bean
    public SecurityManager securityManager(){
        DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
        securityManager.setRealm(getJPARealm());
        return securityManager;
    }

    @Bean
    public JPARealm getJPARealm(){
        JPARealm myShiroRealm = new JPARealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        hashedCredentialsMatcher.setHashIterations(2);
        return hashedCredentialsMatcher;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

注册

    /**
     * 注册操作
     * @param user
     * @return
     */
    @PostMapping(value = "/foreregister")
    public Object register(@RequestBody User user){
        //对用户名进行转义:防止恶意用户名
        user.setName(HtmlUtils.htmlEscape(user.getName()));
        //校验用户名是否存在:存在就提示前台
        boolean exis = userService.isExis(user.getName());
        if(exis){
            return Result.fail("用户名已经存在!");
        }
        //生成一个盐
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        //加密 加盐。md5加密 次数为两次
        String encodedPassword = new SimpleHash("md5",user.getPassword(),salt,2).toString();
        user.setPassword(encodedPassword);
        user.setSalt(salt);

        userService.add(user);

        return Result.success();
    }

登录

    /**
     * 用户登录
     * @param userParam
     * @param session
     * @return
     */
    @PostMapping(value = "/forelogin")
    public Object forelogin(@RequestBody User userParam, HttpSession session){
        //对用户名进行转义:防止恶意用户名
        userParam .setName(HtmlUtils.htmlEscape(userParam .getName()));
        //对用户名和密码进行校验
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userParam.getName(), userParam.getPassword());
        try {
            subject.login(token);
            User user = userService.getByName(userParam.getName());
            session.setAttribute("user", user);
            return Result.success();
        } catch (AuthenticationException e) {
            String message ="账号密码错误";
            return Result.fail(message);
        }
    }

退出登录

    /**
     * 退出登录
     * @return
     */
    @GetMapping("/forelogout")
    public String logout(HttpSession session ) {
        Subject subject = SecurityUtils.getSubject();
        if(subject.isAuthenticated())
            subject.logout();
        return "redirect:home";
    }

拦截器判断是否登录

/**
 * @author langao_q
 * @create 2019-12-18 15:20
 * 登录拦截器:校验是否登录
 * preHandle:在业务处理器处理请求之前被调用(常用)
 * postHandle:在业务处理器处理请求执行完成后,生成视图之前执行(少)
 * afterCompletion:在DispatcherServlet完全处理完请求后被调用,可用于清理资源等(少)
 */
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
        HttpSession session = httpServletRequest.getSession();
        String contextPath = httpServletRequest.getServletContext().getContextPath();
        //需要登录的页面url
        String[] requireAuthPages = new String[]{
                "buy",
                "alipay",
                "payed",
                "cart",
                "bought",
                "confirmPay",
                "orderConfirmed",

                "forebuyone",
                "forebuy",
                "foreaddCart",
                "forecart",
                "forechangeOrderItem",
                "foredeleteOrderItem",
                "forecreateOrder",
                "forepayed",
                "forebought",
                "foreconfirmPay",
                "foreorderConfirmed",
                "foredeleteOrder",
                "forereview",
                "foredoreview"
        };

        //当前请求的url
        String uri = httpServletRequest.getRequestURI();

        //去掉指定字符串
        uri = StringUtils.remove(uri, contextPath + "/");
        String page = uri;

        //判断是否是属于需要登录的url
        if(beginWith(page, requireAuthPages)){
            //校验用户是否登录- shiro判断
            Subject subject = SecurityUtils.getSubject();
            if(!subject.isAuthenticated()) {
                httpServletResponse.sendRedirect("login");
                return false;
            }
        }

        return true;
    }

    /**
     * 比较page是否存在于requiredAuthPages中
     * startsWith() 方法用于检测字符串是否以指定的前缀开始。
     * @param page
     * @param requiredAuthPages
     * @return
     */
    private boolean beginWith(String page, String[] requiredAuthPages){
        boolean result = false;
        for(String requiredAuthPage : requiredAuthPages){
            if(StringUtils.startsWith(page, requiredAuthPage)){
                result = true;
                break;
            }
        }
        return result;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {

    }
}

判断是否登录

    /**
     * 校验用户是否登录
     * @param session
     * @return
     */
    @GetMapping(value = "/forecheckLogin")
    public Object checkLogin(HttpSession session){
        Subject subject = SecurityUtils.getSubject();
        if(subject.isAuthenticated())
            return Result.success();
        else
            return Result.fail("未登录");
    }