<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>${thymeleaf-shiro.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.xmlunit</groupId> <artifactId>xmlunit-core</artifactId> </dependency> </dependencies>
#server.servlet.context-path=/hello ##端口号 server.port=8888 ##检查 mybatis 配置是否存在,一般命名为 mybatis-config.xml mybatis.check-config-location =true ##配置文件位置 Resource下mybaits文件夹 mybatis.config-location=classpath:mybatis/mybatis-config.xml ## mapper xml 文件地址 Resource下mapper文件夹 mybatis.mapper-locations=classpath*:mapper/*Mapper.xml ##日志级别 com.yang.dao 为包名 #logging.level.com.dgw.springbootandshiro.dao=debug ##数据库url spring.datasource.url=jdbc:mysql://localhost/rbac?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT ##数据库用户名 spring.datasource.username=root ##数据库密码 spring.datasource.password=root ##数据库驱动 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.thymeleaf.check-template-location=true spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.prefix=classpath:/templates/ spring.thymeleaf.suffix=.html # 建议在开发时关闭缓存,不然没法看到实时页面 spring.thymeleaf.cache=false ##去除thymeleaf的html严格校验 spring.thymeleaf.mode=HTML spring.main.allow-bean-definition-overriding=true #debug=true #简单设置一下日志等级 logging.level.web=info logging.level.root=info
目录结构如下:
Mybatis 配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <typeAlias alias="Integer" type="java.lang.Integer" /> <typeAlias alias="Long" type="java.lang.Long" /> <typeAlias alias="HashMap" type="java.util.HashMap" /> <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" /> <typeAlias alias="ArrayList" type="java.util.ArrayList" /> <typeAlias alias="LinkedList" type="java.util.LinkedList" /> <typeAlias alias="user" type="com.dgw.springbootandshiro.bean.User"/> </typeAliases> </configuration>
实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String username; private String password; private String salt; } @Data @AllArgsConstructor @NoArgsConstructor public class Role { private Integer id; private String roleName; private Date createTime; } @Data @AllArgsConstructor @NoArgsConstructor public class Permission { private Integer id; private String permissionName; private Date createTime; }
dao接口: 部分 其余扎着框框画鸭蛋
@Mapper public interface UserMapper { User queryUserByUsername(@Param("username") String username); Integer insertUser(User user); }
services实现:
@Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userDAO; @Override @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = SQLException.class) public User queryUserByUsername(String username) { return userDAO.queryUserByUsername(username); } @Override public Integer insertUser(User user) { // 加密 String salt = UUID.randomUUID().toString(); String s = new Sha256Hash(user.getPassword(), salt, MyConstant.INTERCOUNT).toBase64(); // 设置密文 user.setPassword(s); // 设置盐 user.setSalt(salt); return userDAO.insertUser(user); } }
Realm配置i
public class ShiroRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private PermissionService permissionService; // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = (String)principalCollection.getPrimaryPrincipal(); // 查询当前用户的权限信息 Set<String> roles = roleService.queryAllRolenameByUsername(username); Set<String> perms = permissionService.queryAllPermissionByUsername(username); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles); simpleAuthorizationInfo.setStringPermissions(perms); return simpleAuthorizationInfo; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { String usernmae = (String) authenticationToken.getPrincipal(); User user = userService.queryUserByUsername(usernmae); if (user == null) { return null; } //这里会去校验密码是否正确 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName() ); return authenticationInfo; } }
Shiroconfig 配置
@Configuration public class ShiroConfig { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Bean("shiroFilterFactoryBean") public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager") SecurityManager securityManager) { logger.info("启动shiroFilter--时间是:" + new Date()); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //shiro拦截器 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 --> Map<String, Filter> filterMap=new LinkedHashMap<String, Filter>(); filterMap.put("MyRememberFilter", new MyRememberFilter()); shiroFilterFactoryBean.setFilters(filterMap); // 如果不设置默认会自动寻找Web工程根目录下的"/login"页面,即本文使用的login.html shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/main"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); //未授权界面 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // 配置不被拦截的资源及链接 filterChainDefinitionMap.put("/static/**", "anon"); // 退出过滤器 filterChainDefinitionMap.put("/logout", "logout"); //开启注册页面不需要权限 filterChainDefinitionMap.put("/user/login", "anon"); filterChainDefinitionMap.put("/user/register", "anon"); //配置需要认证权限的 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 配置shiro的生命周期 * * @return */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 定义加密规则 存入密码时也必须加密 */ @Bean public HashedCredentialsMatcher myMatcher(){ HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("SHA-256"); // true means hex encoded, false means base64 encoded matcher.setStoredCredentialsHexEncoded(false); matcher.setHashIterations(10000); return matcher; } public class MyRememberFilter extends FormAuthenticationFilter { protected boolean isAccessAllowed(HttpServletRequest request, HttpServletResponse response, Object mappedValue){ Subject subject=getSubject(request,response); if(!subject.isAuthenticated() && subject.isRemembered()){ if(subject.getSession().getAttribute("user")==null &&subject.getPrincipal()!=null){ subject.getSession().setAttribute("user",subject.getPrincipal()); } } return subject.isAuthenticated() || subject.isRemembered(); } } /* private class MyMatcher extends HashedCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token; String pwd = encrypt(String.valueOf(usernamePasswordToken.getPassword())); String mysqlpwd = (String) info.getCredentials(); logger.info("密码"+mysqlpwd); logger.info("密码"+pwd); return this.equals(pwd, mysqlpwd); } //将传进来的密码进行加密的方法 private String encrypt(String data) { // 加密 String salt = UUID.randomUUID().toString(); String s = new Sha256Hash(data, salt, MyConstant.INTERCOUNT).toBase64(); return s; } }*/ /** * 自定义身份认证Realm(包含用户名密码校验,权限校验等) */ @Bean public ShiroRealm myShiroRealm() { ShiroRealm myShiroRealm = new ShiroRealm(); myShiroRealm.setCredentialsMatcher(myMatcher()); return myShiroRealm; } /** * 使用shiro 支持thymeleaf 模版引擎 */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } /** * 配置记住我Cookie对象参数,rememberMeCookie()方法是设置Cookie的生成模版 */ public SimpleCookie rememberMeCookie() { //这个参数是cookie的名称,对应前端的checkbox的name=rememberMe SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); //cookie生效时间为10秒 simpleCookie.setMaxAge(100000); return simpleCookie; } /** * 配置Cookie管理对象,rememberMeManager()方法是生成rememberMe管理器 */ @Bean public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); return cookieRememberMeManager; } /** * 配置sessionCookie对象参数,sessionCookie()方法是设置Cookie的生成模版 */ public SimpleCookie sessionIdCookie() { //这个参数是cookie的名称,对应前端的checkbox的name=rememberMe SimpleCookie simpleCookie = new SimpleCookie("JSESSIONID"); //只允许http请求访问cookie simpleCookie.setHttpOnly(true); //cookie生效时间为10秒 默认为-1 simpleCookie.setMaxAge(-1); return simpleCookie; } /** * 配置Cookie管理对象,rememberMeManager()方法是生成rememberMe管理器 */ @Bean public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager(); webSessionManager.setSessionIdCookie(sessionIdCookie()); // session全局超时时间, 单位:毫秒 ,30分钟 默认值为1800000 webSessionManager.setGlobalSessionTimeout(1800000); //开启检测器,默认开启 webSessionManager.setSessionIdUrlRewritingEnabled(true); // 检测间隔事件 时间为1小时 webSessionManager.setSessionValidationInterval(3600000); // 设置监听器 //webSessionManager.setSessionListeners(); return webSessionManager; } @Bean public CacheManager cacheManager() { return new MemoryConstrainedCacheManager(); } @Bean(name = "securityManager") public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //注入自定义myRealm securityManager.setRealm(myShiroRealm()); //注入自定义cacheManager securityManager.setCacheManager(cacheManager()); //注入记住我管理器 securityManager.setRememberMeManager(rememberMeManager()); //注入自定义sessionManager securityManager.setSessionManager(sessionManager()); return securityManager; } //开启shiro aop注解支持,不开启的话权限验证就会失效 @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor(); sourceAdvisor.setSecurityManager(securityManager); return sourceAdvisor; } //配置异常处理,不配置的话没有权限后台报错,前台不会跳转到403页面 @Bean(name = "simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 mappings.setProperty("UnauthorizedException", "403"); simpleMappingExceptionResolver.setExceptionMappings(mappings); // None by default simpleMappingExceptionResolver.setDefaultErrorView("403"); // No default simpleMappingExceptionResolver.setExceptionAttribute("ex"); // Default is "exception" return simpleMappingExceptionResolver; } }
login.html
<!DOCTYPE html> <html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" xmlns:th="http://www.thymeleaf.org" > <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>Insert title here</title> </head> <body> <form method="post" action="login" > <label for="name">用户名</label> <shiro:authenticated> <input id="name" type="text" name="username" value="<shiro:principal/>" > </shiro:authenticated> <shiro:notAuthenticated> <input id="name" type="text" name="username" value=" " > </shiro:notAuthenticated> <br> <label for="pass">密码</label> <input id="pass" type="text" name="password"> <button type="submit">提交</button> </form> </body> </html>
@Controller @RequestMapping("/user") public class LoginController { private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private UserService userService; @GetMapping("/login") public String login() { return "login"; } @PostMapping("/login") public String loginLogic(User user) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); // 登录失败会抛出异常,则交由异常解析器处理 token.setRememberMe(true); subject.login(token); return "main"; } @GetMapping("/register") public String regiter() { return "register"; } @PostMapping("/register") public String logicRegiter(User user) { userService.insertUser(user); return "redirect:login"; } }