角色权限控制
基于UserDetails 数据库认证
- Maven导入依赖
org.springframework.security
spring-security-web
5.0.1.RELEASE
org.springframework.security
spring-security-config
5.0.1.RELEASE
org.springframework.security
spring-security-core
5.0.1.RELEASE
org.springframework.security
spring-security-taglibs
5.0.1.RELEASE
- web.xml配置
contextConfigLocation
classpath:spring-security.xml
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
- spring-security两种配置方式
1.xml配置
2.实体类配置
(1) spring-security.xml配置文件
(2)实体类配置方式
@Component
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
/****
* 1、放行配置
*/
@Override
public void configure(WebSecurity web) throws Exception {
//放行地址
web.ignoring().antMatchers("/css/**");
web.ignoring().antMatchers("/img/**");
web.ignoring().antMatchers("/js/**");
web.ignoring().antMatchers("/plugins/**");
web.ignoring().antMatchers("/*.html");
web.ignoring().antMatchers("/seller/add.shtml");
}
/***
* 2、权限拦截配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//限制访问
http.authorizeRequests().antMatchers("/**").access("hasAnyRole('ADMIN')");
//禁用CSRF
http.csrf().disable();
//发生异常
http.exceptionHandling().accessDeniedPage("/error.html");
//启用iframe
http.headers().frameOptions().disable();
//一个用户只允许在一个地方登录,其他用户登录就会把已登录用户挤掉
http.sessionManagement().maximumSessions(1).expiredUrl("/shoplogin.html");
//配置登录
http.formLogin().loginPage("/shoplogin.html")
.loginProcessingUrl("/login")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//成功响应消息
Result result = new Result(true, "/admin/index.html");
responseLogin(response, result);
}
}).failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//失败响应消息
Result result = new Result(false, "账号或密码不正确!");
responseLogin(response, result);
}
});
//.defaultSuccessUrl("/admin/index.html",true)
//.failureUrl("/shoplogin.html");
//配置登出
http.logout().logoutUrl("/logout")
.invalidateHttpSession(true)
.logoutSuccessUrl("/shoplogin.html");
}
/***
* 响应用户登录
* @param response
* @param result
* @throws IOException
*/
public void responseLogin(HttpServletResponse response, Result result) throws IOException {
//设置编码格式
response.setContentType("application/json;charset=utf-8");
//将Result转成JSON字符
String jsonString = JSON.toJSONString(result);
//输出数据
PrintWriter writer = response.getWriter();
writer.write(jsonString);
writer.flush();
writer.close();
}
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private BCryptPasswordEncoder encoder;
/***
* 3、授权认证
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//写死
//auth.inMemoryAuthentication().withUser("admin").password("123456").roles("ADMIN");
//自定义授权认证类
auth.userDetailsService(userDetailsService)
.passwordEncoder(encoder); //指定加密对象
}
- userService实现类
自定义IUserService 类 继承 UserDetailsService
package com.itheima.ssm.service;
import org.springframework.security.core.userdetails.UserDetailsService;
public interface IUserService extends UserDetailsService {
}
使用 自定义UserServiceImpl 类实现 IUserService的接口
package com.itheima.ssm.service.impl;
import com.itheima.ssm.dao.IUserDao;
import com.itheima.ssm.domain.Role;
import com.itheima.ssm.domain.UserInfo;
import com.itheima.ssm.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
//注意'userService'与spring-security.xml 里面的同名
//
@Service("userService")
@Transactional
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
try {
userInfo = userDao.findByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
//处理自己的用户对象封装成UserDetails
// User user=new User(userInfo.getUsername(),"{noop}"+userInfo.getPassword(),getAuthority(userInfo.getRoles()));
//由于密码是明文,不安全的,要加上解析"{noop}"
User user = new User(userInfo.getUsername(), "{noop}" + userInfo.getPassword(), userInfo.getStatus() == 0 ? false : true, true, true, true, getAuthority(userInfo.getRoles()));
return user;
}
//作用就是返回一个List集合,集合中装入的是角色描述
public List getAuthority(List roles) {
List list = new ArrayList<>();
for (Role role : roles) {
list.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
}
return list;
}
}
对密码进行加密 BCryptPasswordEncoder
修改spring-secuity.xml,配置BCryptPasswordEncoder类("id = passwordEncoder"),并引用到数据库配置。
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
public void save(UserInfo userInfo) throws Exception{
String newPassword = bCryptPasswordEncoder.encode(userInfo.getPassword());
userInfo.setPassword(newPassword);
userDao.save(userInfo);
}
此时 可以去掉密码"{noop}"声明
public class UserServiceImpl implements IUserService {
@Autowired
private IUserDao userDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserInfo userInfo = null;
try {
userInfo = userDao.findByUsername(username);
} catch (Exception e) {
e.printStackTrace();
}
//处理自己的用户对象封装成UserDetails
// User user=new User(userInfo.getUsername(),"{noop}"+userInfo.getPassword(),getAuthority(userInfo.getRoles()));
//由于密码是明文,不安全的,要加上解析"{noop}"
User user = new User(userInfo.getUsername(), userInfo.getPassword(), userInfo.getStatus() == 0 ? false : true, true, true, true, getAuthority(userInfo.getRoles()));
return user;
}
}
方法级别权限控制
-
JSR250
注解:
@RolesAllowed(常用) 表示访问对应方法时所应该具有的角色
@PermitAll 表示允许所有角色进行访问,即不进行权限控制
@DenyAll 与PermitAll 相反,无论什么角色都不能访问
- maven导入依赖
javax.annotation
jsr250-api
1.0
- spring-security.xml 开启命令支持
public class ProductController{
@RequestMapping("/findAll.do")
@RolesAllowed("ADMIN") //只有ADMIN角色可用
public ModelAndView findAll() throws Exception {
ModelAndView mv = new ModelAndView();
List ps = productService.findAll();
mv.addObject("productList", ps);
mv.setViewName("product-list1");
return mv;
}
}
-
Secured注解
1.属于spring框架,无需导入依赖
2.相比JSR250,权限必须加上"ROLE_XXX",例如 ROLE_ADMIN
- spring-security.xml开启命令支持
public class OrdersController {
@Autowired
private IOrdersService ordersService;
@RequestMapping("/findAll.do")
@Secured("ROLE_ADMIN") //此处ROLE必须加上
public ModelAndView findAll(@RequestParam(name = "page", required = true, defaultValue = "1") int page, @RequestParam(name = "size", required = true, defaultValue = "4") int size) throws Exception {
ModelAndView mv = new ModelAndView();
List ordersList = ordersService.findAll(page, size);
//PageInfo就是一个分页Bean
PageInfo pageInfo = new PageInfo(ordersList);
mv.addObject("pageInfo", pageInfo);
mv.setViewName("orders-page-list");
return mv;
}
-
表达式注解
1.@PreAuthorize在方法调用前,基于表达式的计算结果来限制 对方法的访问
示例:
@PreAuthorize("#userId == authentication.principal.userId or hasAuthority('ADMIN')")
void changePassword(@P("userId") long userId){ }
2.@PostAuthorize允许方法调用,但是如果表达式计算结果为false,将抛出异常
示例:
@PostAuthorize
User getUser(returnObject.serId==authentication.principal.userId or hasPermission(returnObject,'ADMIN'));
- spring-security.xml开启
@RequestMapping("/save.do")
//只有当前登录是tom用户 才可以操作
@PreAuthorize("authentication.principal.username=='tom'")
public String save(UserInfo userInfo) throws Exception{
userService.save(userInfo);
return "redirect:findAll.do";
}
@RequestMapping("/findAll.do")
//只有admin用于才可以操作
@PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView findAll() throws Exception {
ModelAndView mv = new ModelAndView();
List userList = userService.findAll();
mv.addObject("userList",userList);
mv.setViewName("user-list");
return mv;
}
Spring Security允许我们在定义URL访问或方法访问所应有的权限时使用Spring EL表达式,在定义所需的访问权限时如果对应的表达式返回结果为true则表示拥有对应的权限,反之则无。Spring Security可用表达式对象的基类是SecurityExpressionRoot,其为我们提供了如下在使用Spring EL表达式对URL或方法进行权限控制时通用的内置表达式。
表达式 描述
hasRole([role]) 当前用户是否拥有指定角色。
hasAnyRole([role1,role2]) 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回true。
hasAuthority([auth]) 等同于hasRole
hasAnyAuthority([auth1,auth2]) 等同于hasAnyRole
Principle 代表当前用户的principle对象
authentication 直接从SecurityContext获取的当前Authentication对象
permitAll 总是返回true,表示允许所有的
denyAll 总是返回false,表示拒绝所有的
isAnonymous() 当前用户是否是一个匿名用户
isRememberMe() 表示当前用户是否是通过Remember-Me自动登录的
isAuthenticated() 表示当前用户是否已经登录认证成功了。
isFullyAuthenticated() 如果当前用户既不是一个匿名用户,同时又不是通过Remember-Me自动登录的,则返回true。
页面端 权限控制
- 导入maven依赖
org.springframework.security
spring-security-taglibs
5.3.1.RELEASE
- jsp页面引入
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
- 页面标签关键词
authentication 获取当前正在操作的用户信息
authorize 控制页面某些标签是否显示
- 示例 1
jsp页面展示当前用户名