首先上几张图来看看今天要完成什么任务
1.需要实现的功能
登录拦截 任何请求都会拦截到登录页面(拦截器)
2.验证用户名密码是否正确(Shiro)
2.什么是Shiro?了解Shiro
Shiro是Apache下的一个开源项目。shiro属于轻量级框架,相对于SpringSecurity简单的多,也没有SpringSecurity那么复杂。
简单的来说Shiro是一个Java Web安全验证框架,可以进行验证授权等操作十分的简单方便所以深受大家喜欢。
shiro主要有三大功能模块:
- Subject:主体,一般指用户。
- SecurityManager:安全管理器,管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)
- Realm:用于进行权限信息的验证,一般需要自己实现。
shiro实现原理理解
如图所示一个简单的Shiro控制就是利用代码通过Subject来进行认证和授权,而Subject又委托给 SecurityManager;我们需要给Shiro的SecurityManager注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
3.通过项目学习(简单实现):
首先我们的项目结构
在pom.xml添加Shiro依赖
<!--整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.0</version>
</dependency>
导入静态资源,和Thymeleaf等准备工作
正式开始操作
首先我们先解决第一个任务:登录拦截 任何请求都会拦截到登录页面(拦截器)
LoginHanderInterceptor.java
/**
* 拦截器在Controller处理
*
* @Author MRyan
* @Date 2020/2/15 11:03
* @Version 1.0
*/
public class LoginHanderInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object loginUser = request.getSession().getAttribute("loginUser");//获取session对象
if (loginUser == null) {
request.setAttribute("msg", "没有权限,请先登录");
request.getRequestDispatcher("/login.html").forward(request, response);//可以用来把当前request传递到该资源,参数为请求地址 或者把新的资源包括到当前响应中 类似model 传递给login.html
return false;
} else {
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
当我们启动项目的时候任何操作都会被HandlerInterceptor所处理,通过session检测到我们未登录后进行拦截并将拦截信息传递给login.html页面展示
将自定义拦截类注册到WebMvcConfigurer 并托管给Springboot
MyMvcConfig.java
/**
* @Author MRyan
* @Date 2020/2/15 10:47
* @Version 1.0
*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
/**
* 自定义视图解释器
*
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//访问任何请求跳转都到登录页面
registry.addViewController("/").setViewName("login");
registry.addViewController("/main.html").setViewName("index");
registry.addViewController("/login.html").setViewName("login");//这个地方需要注意 一定要有目的是跳转登录请求
}
/**
* 添加自定义拦截器
*
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginHanderInterceptor()).addPathPatterns("/**").
excludePathPatterns("/",
"/login.html",//不要忘记把index.html排除拦截范围
"/css/**",
"/js/**",
"/noauth",
"/imgs/**",
"/index",
"/user/**")
;//拦截所有请求路径除了 login请求路径 定义静态资源路径
}
}
由此实现了任何请求都会跳转到登录页面并将拦截信息展示交互
接下来我们来实现第二个任务:验证用户名密码是否正确(Shiro)
首先我们自定义MyShiroConfig
其实这就是死代码,代码编写从下至上
//ShiroFilterFactoryBean 通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制。
//DefaultWebSecurityManager //设置关联Realm
// UserRealm 进行一些授权认证操作
MyShiroConfig.java
/**
* @Author MRyan
* @Date 2020/2/15 12:34
* @Version 1.0
*/
@Configuration//托管给Springboot
public class MyShiroConfig {
//ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("DefaultWebSecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加Shiro内置过滤器
/*
anon:无需认证就可以访问
authc:必须认证了才能访问
user:必须拥有 记住我 功能才能访问
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
* */
//拦截
//授权 正常情况下,没有授权会跳到未授权页面
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/main.html", "perms[user:root]");//设置首页必须认证才能访问 并赋予权限
bean.setFilterChainDefinitionMap(filterMap);
//设置登录的请求
bean.setLoginUrl("/login");
//设置未授权跳转请求
bean.setUnauthorizedUrl("/noauth");
return bean;
}
//DefaultWebSecurityManager
@Bean("DefaultWebSecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("UserRealm") UserRealm userRealm) {//@Qualifier 选择和参数中同名的bean
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置关联Realm
securityManager.setRealm(userRealm);
return securityManager;
}
// UserRealm
@Bean("UserRealm")
public UserRealm getRealm() {
return new UserRealm();
}
}
UserRealm.java
/**
* @Author MRyan
* @Date 2020/2/15 12:40
* @Version 1.0
*/
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//在这里进行一些授权 分发权限等等
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();//接受AuthenticationInfo传递过来的User对象
/* System.out.println(user.getShiro());*/
info.addStringPermission(user.getShiro());//设置权限
return info;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//模拟数据库中获取数据
User user = new User();
user.setUsername("root");
user.setPassword("123456");
user.setShiro("user:root");
UsernamePasswordToken usertoken = (UsernamePasswordToken) token;//全局关系
if (!usertoken.getUsername().equals(user.getUsername())) {
return null;//跑出异常给LoginController
}
return new SimpleAuthenticationInfo(user, user.getPassword(), "");//Shrio自己做加密 第一个参数传递
user对象 第二个参数传递密码
}
}
接下来是登录Controller
/**
* @Author MRyan
* @Date 2020/2/15 13:29
* @Version 1.0
*/
@Controller
public class LoginController {
@GetMapping("/user/login")
public String login(String username, String password,
Model model) {//简单实验 未做加密 post请求 捏造数据模拟数据库登录
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
try {
subject.login(usernamePasswordToken);//登录成功 返回首页
return "redirect:/main.html";
} catch (UnknownAccountException e) {//抛出异常用户名不存在
model.addAttribute("msg", "用户名不存在");
return "login";
} catch (IncorrectCredentialsException e) {//抛出异常密码不存在
model.addAttribute("msg", "密码错误");
return "login";
}
}
}
由此可见Shiro的强大之处了,很神奇将配置实现连接起来,我们什么都不用管都交给Shiro即可。
4.成果:
接下来开启项目看效果吧