一般来说,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体内容