一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--引入Thymeleaf依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--Security与Thymeleaf整合--> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> <version>3.0.4.RELEASE</version> </dependency>
2. 编写数据库查询该用户的服务类,以便授权调用
注意:
public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //以下两个是分别获取登陆用户的角色和权限,可以选择利用角色作为授权,或者权限作为授权 Collection<GrantedAuthority> roleauthorities = new ArrayList<>(); /* Collection<GrantedAuthority> pageauthorities = new ArrayList<>();*/ //角色 数据库获取该用户的角色名 Set<role> roles = roleService.roles(username); //权限 数据库获取该用户权限名 /*List<user_Page> user_pages = userPageService.user_Page_List(username);*/ //将用户角色放入roleauthorities if(roles != null){ for (role role : roles) { SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getRolename()); roleauthorities.add(authority); } } //将用户权限放入pageauthorities /* if (user_pages !=null && !user_pages.isEmpty()){ for (user_Page userPage:user_pages){ SimpleGrantedAuthority authority = new SimpleGrantedAuthority(userPage.getRoleCode()); pageauthorities.add(authority); } }*/ //根据角色授权还是根据权限授权自己选,此处,根据用户角色授权 UserDetails roleuserDetails = new User(login.getLoginusername(),bCryptPasswordEncoder.encode(login.getLoginpassword()),roleauthorities); /* UserDetails pageuserDetails = new User(login.getLoginusername(),bCryptPasswordEncoder.encode(login.getLoginpassword()),pageauthorities);*/ return roleuserDetails; } }
3. 编写配置类
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { //定制请求的授权规则 @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/").permitAll() .antMatchers("/user/**").hasAnyRole("ROLE_USER","ROLE_ADMIN") .antMatchers("/admin/**").hasRole("ROLE_ADMIN") /*.antMatchers("/user/**").hasAnyAuthority("user","admin")*/ /*.antMatchers("/admin/**").hasAuthority("admin")*/ /*hasIpAddress("127.0.0.1") 只有发送的Ip匹配时才允许*/ //开启自动配置的登录功能:如果没有权限,就会跳转到登录页面! http.formLogin() .usernameParameter("username") //前端传来的表单name格式 .passwordParameter("password") //前端传来的表单name格式 .loginPage("/toLogin") //没有登录所跳转到的登录页 .loginProcessingUrl("/login"); // 登陆表单提交请求 //开启自动配置的注销的功能 // /logout 注销请求 // .logoutSuccessUrl("/"); 注销成功来到首页 http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求 http.logout().logoutSuccessUrl("/");// 注销成功来到首页 //记住我 http.rememberMe().rememberMeParameter("remember"); } //定义认证规则 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //spring security 官方推荐的是使用bcrypt加密方式,基于内存的验证 /*auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()) .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("ROLE_ADMIN","ROLE_USER") .and() .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("ROLE_USER");*/ //基于数据库的认证 auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder()); } }
4. 与thymeleaf的整合
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"> <div sec:authorize="!isAuthenticated()> //没有登录,就显示。 </div> <div sec:authorize="isAuthenticated()> //登录后才显示 用户名:<span sec:authentication="principal.username"></span> 角色:<span sec:authentication="principal.authorities"></span> </div> <div sec:authorize="hasRole('ROLE_ADMIN')"> //admin角色才看得见 </div> <div sec:authorize="hasAnyRole('ROLE_ADMIN','ROLE_USER')"> //admin和user角色才看得见 </div> <div sec:authorize="hasAuthority('admin')"> //admin权限的用户就能看到 </div> <div sec:authorize="hasAnyAuthority('admin','user')"> //admin和user权限的用户就能看到 </div>
<!--导入shiro安全框架--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.5.1</version> </dependency> <!--引入Thymeleaf依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- thymelealf与shiro的整合 --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
2. 自定义UserRealm类:用于查询用户的角色和权限信息并保存到权限管理器
public class UserRealm extends AuthorizingRealm { @Autowired private UserServiceimpl userServiceimpl; //数据库操作的实现类 //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //拿到当前登录的用户信息 Subject subject = SecurityUtils.getSubject(); User currentUser = (User) subject.getPrincipal(); /*for (Role role : currentUser.getRoles()) { //添加角色 info.addRole(role.getRoleName()); //添加权限 for (Permissions permissions : role.getPermissions()) { info.addStringPermission(permissions.getPermissionsName()); } }*/ if(currentUser.getLevel() == 1) { //根据数据库表对应该用户存储的等级进行权限授权 info.addStringPermission("user:ordinary"); }else if(currentUser.getLevel() == 2){ info.addStringPermission("user:admin"); }else if(currentUser.getLevel() == 3){ info.addStringPermission("user:admin"); } return info; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken userToken = (UsernamePasswordToken) token; //获取控制层传来的token信息。 User user = userServiceimpl.queryByUnameOrEmail(userToken.getUsername()); if (user == null) {//没有这个用户 return null;//抛出UnknownAccountException的异常 } String passwordMD5 = user.getPwdMD5(); //获取该用户数据库用户表中储存的加密后的密码。 Subject currentSubject = SecurityUtils.getSubject(); Session session = currentSubject.getSession(); session.setAttribute("loginUser",user); //将该用户信息放入当前会话中 //可以加密, MD5,MD5盐值加密 //密码认证shiri自动做,已经加密 ByteSource salt = ByteSource.Util.bytes(user.getUsername()); //MD5盐值加密 return new SimpleAuthenticationInfo(user, passwordMD5,salt,""); } }
3. 配置Shiro配置类 把UserRealm和SecurityManager等加入到spring容器,顺便配置cookie,session和密码加密配置。
@Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) { //设置安全管理器 ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean(); bean.setSecurityManager(defaultWebSecurityManager); //添加shiro的内置过滤器 /* anon:无需认证可以访问 authc:必须认证才可以访问 user:必须拥有 记住我 功能才能用 perms:拥有对某个资源的权限才能访问 role:拥有某个角色权限才能访问 logout:退出登录 */ Map<String, String> filterMap = new LinkedMap(); //权限操作 filterMap.put("/login", "anon"); filterMap.put("/logout", "authc"); filterMap.put("/", "anon"); filterMap.put("/function/*", "authc"); filterMap.put("/home","authc,user"); filterMap.put("/admin/*","authc,perms[user:admin]"); filterMap.put("/user/*","authc,user"); filterMap.put("/index", "authc,user"); bean.setFilterChainDefinitionMap(filterMap); //设置登录的请求 bean.setLoginUrl("/login"); //设置为授权的请求 bean.setUnauthorizedUrl("/noauth"); return bean; } @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //关联UserRealm securityManager.setRealm(userRealm); //关联记住我功能 securityManager.setRememberMeManager(rememberMeManager()); securityManager.setSessionManager(sessionManager()); return securityManager; } //创建realm对象,自定义对象类 @Bean(name = "userRealm") public UserRealm userRealm() { UserRealm userRealm = new UserRealm(); // 告诉realm,使用credentialsMatcher加密算法类来验证密文 userRealm.setCredentialsMatcher(hashedCredentialsMatcher()); userRealm.setCachingEnabled(false); return userRealm; } /** * cookie对象 * @return */ public SimpleCookie rememberMeCookie() { // 设置cookie名称,对应登录页面的记住我radio的name SimpleCookie cookie = new SimpleCookie("rememberMe"); // 设置cookie的过期时间,单位为秒,这里为7天 cookie.setMaxAge(86400*7); return cookie; } /** * cookie管理对象 * @return */ public CookieRememberMeManager rememberMeManager() { CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); // rememberMe cookie加密的密钥 cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag==")); return cookieRememberMeManager; } /** * shiro session的管理 */ public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); //设置url重新setSessionIdUrlRewritingEnabled值为false sessionManager.setSessionIdUrlRewritingEnabled(false); return sessionManager; } /** * 加密配置 * @return */ @Bean(name = "credentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); // 散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashAlgorithmName("md5"); // 散列的次数,比如散列两次,相当于 md5(md5("")); hashedCredentialsMatcher.setHashIterations(1024); // storedCredentialsHexEncoded默认是true,此时用的是密码加密用的是Hex编码;false时用Base64编码 hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true); return hashedCredentialsMatcher; } //整合shiroDialect @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } }
4. controller层中对登录的相关操作(部分代码)
@PostMapping("/toLogin") public String login(@RequestParam Map<String, Object> formData, Model model, RedirectAttributes redirectAttributes) { //获取当前的用户 Subject subject = SecurityUtils.getSubject(); //封装当前用户信息成token UsernamePasswordToken token = new UsernamePasswordToken((String) formData.get("username"), (String) formData.get("password")); //设置记住我 if (formData.get("rememberMe") == "true") { token.setRememberMe(true); } try { subject.login(token);//执行登录的方法。没有异常就成功! userMapper.addUserView((String) formData.get("username")); return "redirect:/index"; //成功登录~ } catch (UnknownAccountException e) { //用户名不存在 model.addAttribute("msg", "用户名或邮箱不存在!"); model.addAttribute("success", false); return "login"; } catch (IncorrectCredentialsException e) {//密码错误 model.addAttribute("msg", "密码错误!"); model.addAttribute("success", false); model.addAttribute("username",formData.get("username")); return "login"; } }
5. Thymeleaf与Shiro的整合操作
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <shiro:authenticated> //登录认证后可看 <a href="/1">1111</a> </shiro> <shiro:notAuthenticated> //未登录认证可看,记住我的cookies的自动登录也算。 <a href="/1">1111</a> </shiro> <div shiro:hasPermission="user:admin"> //只有管理员权限能看见 <a href="/1">1111</a> </div> <div shiro:hasPermission="user:ordinary"> //用户权限可见 <a href="/2">2222</a> </div> <div shiro:hasRole="user"> //用户角色可以见 <a href="/2">2222</a> </div>
shiro标签的相关说明:
guest标签 <shiro:guest> 用户没有身份验证时显示相应信息,即游客访问信息。 </shiro:guest> user标签 <shiro:user> 用户已经身份验证/记住我登录后显示相应的信息。 </shiro:user> authenticated标签 <shiro:authenticated> 用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。 </shiro:authenticated> notAuthenticated标签 <shiro:notAuthenticated> </shiro:notAuthenticated> 用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。 principal标签 <shiro: principal/> 相当于((User)Subject.getPrincipals()).getUsername()。 <shiro:principal property="username"/> lacksPermission标签 <shiro:lacksPermission name="org:create"> 如果当前Subject没有权限将显示body体内容。 </shiro:lacksPermission> hasRole标签 <shiro:hasRole name="admin"> 如果当前Subject有角色将显示body体内容。 </shiro:hasRole> hasAnyRoles标签 <shiro:hasAnyRoles name="admin,user"> //如果当前Subject有任意一个角色(或的关系)将显示body体内容。 </shiro:hasAnyRoles> lacksRole标签 <shiro:lacksRole name="abc"> </shiro:lacksRole> 如果当前Subject没有角色将显示body体内容。 hasPermission标签 <shiro:hasPermission name="user:admin"> </shiro:hasPermission> 如果当前Subject有权限将显示body体内容
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句