角色权限控制
基于UserDetails 数据库认证
- Maven导入依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
- web.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<!-- 配置加载类路径的配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
</context-param>
<filter>
<!-- “springSecurityFilterChain”为固定命名,不可更改 -->
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
- spring-security两种配置方式
1.xml配置
2.实体类配置
(1) spring-security.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- 配置不拦截的资源 -->
<security:http pattern="/login.jsp" security="none"/>
<security:http pattern="/failer.jsp" security="none"/>
<security:http pattern="/css/**" security="none"/>
<security:http pattern="/img/**" security="none"/>
<security:http pattern="/plugins/**" security="none"/>
<!--
配置具体的规则
auto-config="true" 不用自己编写登录的页面,框架提供默认登录页面
use-expressions="false" 是否使用SPEL表达式(没学习过)
-->
<security:http auto-config="true" use-expressions="false">
<!-- 配置具体的拦截的规则 pattern="请求路径的规则" access="访问系统的人,必须有ROLE_USER的角色" -->
<security:intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN"/>
<!-- 定义跳转的具体的页面 -->
<security:form-login
login-page="/login.jsp"
login-processing-url="/login.do"
default-target-url="/index.jsp"
authentication-failure-url="/failer.jsp"
authentication-success-forward-url="/pages/main.jsp"
/>
<!-- 关闭跨域请求 -->
<security:csrf disabled="true"/>
<!-- 退出 -->
<security:logout invalidate-session="true" logout-url="/logout.do" logout-success-url="/login.jsp" />
</security:http>
<!-- 切换成数据库中的用户名和密码 -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<!-- 配置加密的方式-->
<!-- <security:password-encoder ref="passwordEncoder"/>-->
</security:authentication-provider>
</security:authentication-manager>
<!-- 配置加密类 -->
<!-- <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>-->
<!-- 提供了入门的方式,在内存中存入用户名和密码
<security:authentication-manager>
<security:authentication-provider>
<security:user-service>
<security:user name="admin" password="{noop}admin" authorities="ROLE_USER"/>
</security:user-service>
</security:authentication-provider>
</security:authentication-manager>
-->
</beans>
(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 里面的同名
//<security:authentication-provider user-service-ref="userService">
@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<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
List<SimpleGrantedAuthority> list = new ArrayList<>();
for (Role role : roles) {
list.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleName()));
}
return list;
}
}
对密码进行加密 BCryptPasswordEncoder
修改spring-secuity.xml,配置BCryptPasswordEncoder类("id = passwordEncoder"),并引用到数据库配置。
<!-- 切换成数据库中的用户名和密码 -->
<security:authentication-manager>
<security:authentication-provider user-service-ref="userService">
<!-- 配置加密的方式-->
<security:password-encoder ref="passwordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>
<!-- 配置加密类 -->
<bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
@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导入依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
- spring-security.xml 开启命令支持
<security:global-method-security jsr250-annotations="enabled"/>
public class ProductController{
@RequestMapping("/findAll.do")
@RolesAllowed("ADMIN") //只有ADMIN角色可用
public ModelAndView findAll() throws Exception {
ModelAndView mv = new ModelAndView();
List<Product> 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开启命令支持
<security:global-method-security secured-annotations="enabled"/>
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<Orders> 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开启
<security:global-method-security pre-post-annotations="enabled"/>
@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<UserInfo> 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依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>5.3.1.RELEASE</version>
</dependency>
- jsp页面引入
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
- 页面标签关键词
authentication 获取当前正在操作的用户信息
authorize 控制页面某些标签是否显示
- 示例 1
jsp页面展示当前用户名
<div class="pull-left info>
<p>
<security:authentiaction property= "principal.username"/>
</p>
</div>