SpringBoot&Shiro实现用户认证
实现思路
思路:实现认证功能主要可以归纳为3点
1.定义一个ShiroConfig配置类,配置 SecurityManager Bean , SecurityManager为Shiro的安全管理器,管理着所有Subject;
注:如果有不太清楚shiro的朋友,可以去各大学习平台上学习一下。
2.在ShiroConfig中配置 ShiroFilterFactoryBean ,它是Shiro过滤器工厂类,依赖SecurityManager ;
3.自定义Realm实现类,包含
doGetAuthorizationInfo()
和doGetAuthenticationInfo()
方法 ,
1.导入依赖
我们搭建好Spring Boot web程序后,导入Shiro,Mybatis,mysql, thymeleaf 相关依赖:
<!-- SpringBoot Web容器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- SpringBoot集成mybatis框架 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!-- SpringBoot 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- thymeleaf模版 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- mysql驱动7.0--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> </dependency> <!--druid 数据源监控--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!-- shiro权限 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
2.定义ShiroConfig配置类:
/** * @ClassName ShiroConfig * @Description TODO * @Author fqCoder * @Date 2020/2/29 3:08 * @Version 1.0 */ @Configuration public class ShiroConfig { /** * 这是shiro的大管家,相当于mybatis里的SqlSessionFactoryBean * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //登录 shiroFilterFactoryBean.setLoginUrl("/login"); //首页 shiroFilterFactoryBean.setSuccessUrl("/index"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //页面权限控制 shiroFilterFactoryBean.setFilterChainDefinitionMap(ShiroFilterMapFactory.shiroFilterMap()); //设置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); return shiroFilterFactoryBean; } /** * web应用管理配置 * @param shiroRealm * @return */ @Bean public DefaultWebSecurityManager securityManager(Realm shiroRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(shiroRealm); return securityManager; } /** * 配置realm,用于认证和授权 * @return */ @Bean public MyShiroRealm shiroRealm() { MyShiroRealm shiroRealm = new MyShiroRealm(); return shiroRealm; } }
3.创建ShiroFilterMapFactory类
注意:
1.这里要用LinkedHashMap 保证有序
2.filterChain基于短路机制,即最先匹配原则,
3.像anon、authc等都是Shiro为我们实现的过滤器,我给出了一张表,在文章尾附录,自行查看
/** * @ClassName ShiroFilterMapFactory * @Description TODO * @Author fqCoder * @Date 2020/2/29 3:09 * @Version 1.0 */ public class ShiroFilterMapFactory { public static Map<String, String> shiroFilterMap() { // 设置路径映射,注意这里要用LinkedHashMap 保证有序 LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //对所有用户认证 filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/logout", "logout"); //对所有页面进行认证 filterChainDefinitionMap.put("/**", "authc"); return filterChainDefinitionMap; } }
配置完了ShiroConfig后,实现自己的Realm,然后注入到SecurityManager里
4.实现Realm类
自定义Realm类需要继承 AuthorizingRealm 类,实现 doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可 ,
doGetAuthorizationInfo() 方法是进行授权的方法,获取角色的权限信息
doGetAuthenticationInfo()方法是进行用户认证的方法,验证用户名和密码
/** * @ClassName MyShiroRealm * @Description TODO * @Author fqCoder * @Date 2020/2/29 3:08 * @Version 1.0 */ @Service public class MyShiroRealm extends AuthorizingRealm { @Autowired private UserMapper userMapper; /** * 获取用户角色和权限 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } /** * 登录认证 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //获取用户输入的用户名密码 String username= (String) token.getPrincipal(); String password=new String((char[])token.getCredentials()); System.out.println("用户输入--->username:"+username+"-->password:"+password); //在数据库中查询 User userInfo=userMapper.selectByName(username); if (userInfo == null) { throw new UnknownAccountException("用户名或密码错误!"); } if (!password.equals(userInfo.getPassword())) { throw new IncorrectCredentialsException("用户名或密码错误!"); } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo, // 用户名 userInfo.getPassword(), // 密码 getName() // realm name ); return authenticationInfo; } }
doGetAuthorizationInfo()方法我们留到下一章实现,其中UnknownAccountException
等异常为Shiro自带异常,Shiro具有丰富的运行时AuthenticationException
层次结构,可以准确指出尝试失败的原因。
接下来我们实现dao层!
4.数据层
数据表设计的比较简单,方便操作
CREATE TABLE `tb_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;
然后创建一张对应的用户表User.class
public class User implements Serializable { private Integer id; private String username; private String password; ....get和set方法省略 }
定义接口UserMapper
这里用的是注解 /** * @ClassName UserMapper * @Description TODO * @Author fqCoder * @Date 2020/2/29 3:30 * @Version 1.0 */ public interface UserMapper { @Select("select * from tb_user where username=#{username}") User selectByName(String username); }
注意:记得在Mapper接口上面加一个扫描注解@Mapper或者在boot启动类上加一个@MapperScan(value = "mapper包路径")注解
5.控制层
我们创建一个LoginController.class类
/** * @ClassName LoginController * @Description TODO * @Author fqCoder * @Date 2020/2/29 6:06 * @Version 1.0 */ @Controller public class LoginController { @GetMapping("/login") public String login(){ return "login"; } @GetMapping("/") public String home(){ return "redirect:/index"; } @GetMapping("/index") public String index(Model model){ User user= (User) SecurityUtils.getSubject().getPrincipal(); model.addAttribute("user",user); return "index"; } @PostMapping("login") @ResponseBody public AjaxResult login(User user){ System.out.println("user = " + user); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); //获取Subject 对象 Subject subject= SecurityUtils.getSubject(); try { subject.login(token); return AjaxResult.success("/index"); } catch (UnknownAccountException e) { return AjaxResult.error(e.getMessage()); } catch (IncorrectCredentialsException e) { return AjaxResult.error(e.getMessage()); } } }
6.登录页面
编写login.html页面
这里我只贴重要代码,具体的代码,到github里找哦!
<form id="loginForm"> <input type="text" id="username" name="username" class="text" /> <input type="password" id="password" name="password" /> </form> <div class="signin"> <input id="loginBut" type="button" value="Login" > </div> -------js代码---- <script type="text/javascript"> $.fn.serializeObject = function () { var o = {}; var a = this.serializeArray(); $.each(a, function () { if (o[this.name]) { if (!o[this.name].push) { o[this.name] = [o[this.name]]; } o[this.name].push(this.value); } else { o[this.name] = this.value || ''; } }); return o; }; $(function () { $("#loginBut").click(function () { var arr=$('#loginForm').serializeObject(); $.ajax({ url: '/login', type: 'post', data: arr, dataType: "json", success: function (data) { if (data.code==200){ location.href=data.msg; } else { alert(data.msg); } }, error: function (data) { alert(data.msg); } }) }); }); </script>
7.启动
先看一下目录
启动项目:访问http://localhost:8080/,它会自动拦截,页面重定向到 http://localhost:8080/login
总结:
SpringBoot整合Shiro实现用户认证功能就到此结束了,一些细节代码参见 https://github.com/Slags/springboot-learn/tree/master/1.springboot-shiro-authentication
附录:
1.Shiro拦截机制表
Filter Name | Class | Description |
---|---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter | 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例/static/**=anon |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter | 基于表单的拦截器;如/**=authc ,如果没有登录会跳到相应的登录页面登录 |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter | Basic HTTP身份验证拦截器 |
logout | org.apache.shiro.web.filter.authc.LogoutFilter | 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/),示例/logout=logout |
noSessionCreation | org.apache.shiro.web.filter.session.NoSessionCreationFilter | 不创建会话拦截器,调用subject.getSession(false) 不会有什么问题,但是如果subject.getSession(true) 将抛出DisabledSessionException 异常 |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter | 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例/user/**=perms["user:create"] |
port | org.apache.shiro.web.filter.authz.PortFilter | 端口拦截器,主要属性port(80) :可以通过的端口;示例/test= port[80] ,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样 |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter | rest风格拦截器,自动根据请求方法构建权限字符串;示例/users=rest[user] ,会自动拼出user:read,user:create,user:update,user:delete权限字符串进行权限匹配(所有都得匹配,isPermittedAll) |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter | 角色授权拦截器,验证用户是否拥有所有角色;示例/admin/**=roles[admin] |
ssl | org.apache.shiro.web.filter.authz.SslFilter | SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口443;其他和port拦截器一样; |
user | org.apache.shiro.web.filter.authc.UserFilter | 用户拦截器,用户已经身份验证/记住我登录的都可;示例/**=user |
来源:https://www.cnblogs.com/Slags/p/12383560.html