尚筹网06权限管理
权限管理过程中的相关概念
主体(principal)
使用系统的用户或设备从其他系统远程登陆的用户等等.简单说就是谁使用系统谁就是主体.
验证(authentication)
权限管理系统确认一个主体的身份,允许主体进入系统.简单说就是“主体”证明自己是谁.
笼统的认为就是以前所做的登陆操作.
授权(authorization)
将操作系统的“权力”“授予”“主体”,这样主体就具备了操作系统的特定功能的能力.
所以简单说就是,授权就是给用户分配权限.
权限管理的主流框架
Spring Security
Spring技术栈的组成部分
通过提供完整可扩展的认证和授权支持保护你的应用程序。
特点
· 和Spring无缝整合。
· 全面的权限控制。
· 专门为Web开发而设计。
旧版本不能脱离Web环境使用。
新版本对整个框架进行了分层抽取,分成了核心模块和Web模块。单独引入核心模块就可以脱离Web环境。
· 重量级
Shiro
Apache旗下的轻量级权限控制框架。
特点:
1、轻量级。Shiro主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。
2、通用性。
好处:不局限于Web环境,可以脱离Web环境使用。
缺陷:在Web环境下一些特定的需求需要手动编写代码定制。
在helloworld基础上加入SpringSecurity
1、加入SpringSecurity依赖
<!-- SpringSecurity对Web应用进行权限管理 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>4.2.10.RELEASE</version> </dependency> <!-- SpringSecurity配置 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>4.2.10.RELEASE</version> </dependency> <!-- SpringSecurity标签库 --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>4.2.10.RELEASE</version> </dependency>
2、加入SpringSecurity控制权限的Filter
SpringSecurity使用的是过滤器Filter而不是拦截器Intercepter,意味着SpringSecurity能够管理的不仅仅是SpringMVC中的hanlder请求,还包含web应用中所有请求.比如:项目中的静态资源也会被拦截,从而进行权限控制.
<filter> <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>
特别注意:<filter-name>springsecurityChain</filter-name>标签中必须是SpringSecurityFilterChain.因为springsecurityFilterChain在IOC容器中对应真正执行权限控制的二十几个Filter,只要叫这个名字才能够加载这些Filter.
3、加入配置类
enable理解为启用
@EnableWebSecurity注解表示启用Web安全功能
4、效果
1、所有请求都会被SpringSecurity拦截,要求登陆才可以访问
2、静态资源也都被拦截,要求登陆
3、登陆失败有错误提示
SpringSecurity操作实验
1、放行首页和静态资源
2、未授权请求跳转到登陆页
3、设置登陆系统的账号、密码
4、csrf如何防止跨站请求伪造
cross-site request forgery发送登陆请求时没有携带_csrf值,返回下面错误
面试相关问题:当你登陆系统时,认证中心根据浏览器的cookie识别用户身份.
那如果用户的cookie被劫持仿冒用户身份登陆系统怎么办?
除了cookie之外,还使用_csrf生成的token防止跨站请求伪造.
最后:登陆成功后具体资源都可以访问了.
5、用户注销
如果csrf功能没有禁用,那么退出必须是post方式.
6、基于角色进行访问控制
所属类webAppSecurityConfig
通过HttpSecurity对象设置资源的角色要求
7、自定义403错误页面
8、记住我-内存版
9、记住我数据库版
查询数据库完成认证
了解SringSecurity默认实现
自定义数据库查询方式
使用自定义UserDetailService完成登陆
“ROLE_”前缀问题
应用自定义密码加密规则
public class BCryptPasswordEncoderTest { public static void main(String[] args) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); CharSequence rawPassword = "123123"; for(int i = 0; i < 10; i++) { String encodedPassword = encoder.encode(rawPassword); System.out.println(encodedPassword); } System.out.println(); boolean matches = encoder.matches(rawPassword, "$2a$10$Y2Cq8ilT21ME.lvu6bwcPO/RMkU7ucAZpmFzx7GDTXK9KNxHyEM1e"); System.out.println(matches); } }
众筹项目加入SpringSecurity环境
导入依赖
<!--Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> </dependency>
Filter
<!-- SpringSecurity 的 Filter --> <filter> <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>
配置类
//表示当前类是一个配置类 @Configuration // 启用web环境下权限控制 @EnableWebSecurity // 启用全局方法权限控制功能,设置prePostEnabled = true,保证@PreAuthorize,@PostAuthority,@PreFilter,@PostFilter生效 @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebAppSecurityConfig extends WebSecurityConfigurerAdapter { // @Autowired // private UserDetailsService userDetailsService; // @Autowired // private BCryptPasswordEncoder passwordEncoder; @Bean public BCryptPasswordEncoder getPasswordEncoder(){ return new BCryptPasswordEncoder(); } // @Override // protected void configure(AuthenticationManagerBuilder builder) throws Exception { // //临时使用内存模式测试代码 // //builder.inMemoryAuthentication().withUser("tom").password("123123").roles("ADMIN"); // // builder // .userDetailsService(userDetailsService) // .passwordEncoder(passwordEncoder) // ; // // } @Override protected void configure(HttpSecurity security) throws Exception { security .authorizeRequests() // 对请求进行授权 .antMatchers("/index.jsp") // 登录页面设置 .permitAll() // 无条件访问 .antMatchers("/static/**") // 对静态资源设置 .permitAll() .antMatchers("/admin/get/page.html") // .hasRole("经理") .access("hasRole('经理') or hasAuthority('user:get')") .anyRequest() //其他任意请求 .authenticated() //认证后访问 .and() .exceptionHandling() //自定义异常映射,以免在filter阶段抛出403异常 .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletRequest.setAttribute("exception",new Exception(ConstantUtil.MESSAGE_ACCESS_DENIED)); httpServletRequest.getRequestDispatcher("/WEB-INF/pages/system-error.jsp").forward(httpServletRequest,httpServletResponse); } }) .and() .formLogin() // 开启表单登录功能 .loginPage("/admin/to/login/page.html") // 指定登录页面 .permitAll() .loginProcessingUrl("/security/do/login.html") // 处理登录请求的地址 .permitAll() .usernameParameter("username") // 账号的请求参数名 .passwordParameter("password") // 密码的请求参数名 .defaultSuccessUrl("/admin/to/main/page.html") // 登录成功后跳转的地址 .and() .logout() .logoutUrl("/admin/security/do/logout.html") // 处理退出请求的地址 .logoutSuccessUrl("/admin/to/login/page.html") // 登录退出后跳转的地址 .and() .csrf() // 跨站请求伪造功能 .disable() //取消 ; } }
配置自动扫描的包
考虑到权限控制系统更多的需要控制Web请求,而且有些请求没有经过Service方法,所以在SpringMVC的IOC容器中扫面配置类,但是,SpringSecurity是有管理Service、Dao得到的能力的
<!-- 配置创建 spring 容器要扫描的包 --> <context:component-scan base-package="com.adom.controller,com.adom.exception,com.adom.config"/>
多个IOC容器之间的关系
问题描述:项目启动时控制台抛异常说找不到“springSecurityFilterChain”的bean
问题分析
web组件的加载顺序:Listener-》filter-》Servlet
1、SpringIOC容器:ContextLoaderListener创建
2、SpringMVC IOC容器:DIspatcherServlet创建
3、SpringSecurityFilterChain:从IOC容器中找到对应的bean
ContextLoaderListener初始化后,springSecurityFilterChain就在ContextLoaderListener创建的IOC容器中查找所需要的bean,但是我们没有在ContextLoaderListener的IOC容器中扫描SpringSecurity的配置类,所以SpringSecurityFilterChain对应的bean找不到.
问题解决
将ContextLoaderListener取消,原本由ContextLoaderListener读取的Spring配置文件交给DispatcherServlet负责读取.
<!-- 配置 SpringMVC 的前端控制器 --> <!-- The front controller of this Spring Web application, responsible for handling all application requests --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 以初始化参数的形式指定 SpringMVC 配置文件的位置 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> <!-- 让 DispatcherServlet 在 Web 应用启动时创建对象、初始化 --> <!-- 默认情况:Servlet 在第一次请求的时候创建对象、初始化 --> <load-on-startup>1</load-on-startup> </servlet> <!-- Map all requests to the DispatcherServlet for handling --> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <!-- DispatcherServlet 映射的 URL 地址 --> <!-- 大白话:什么样的访问地址会交给 SpringMVC 来处理 --> <!-- 配置方式一:符合 RESTFUL 风格使用“/” --> <!-- <url-pattern>/</url-pattern> --> <!-- 配置方式二:请求扩展名 --> <url-pattern>*.html</url-pattern> <url-pattern>*.json</url-pattern> </servlet-mapping>
SpringSecurity初始设置
放行首页、静态资源
security .authorizeRequests()// 对请求进行授权 .antMatchers("/index.jsp")// 登录页面设置 .permitAll()// 无条件访问 .antMatchers("/static/**") // 对静态资源设置 .permitAll()
登陆
SpringSecurity开启表单登陆功能并前往登陆表单页面
.and() .formLogin() // 开启表单登录功能 .loginPage("/admin/to/login/page.html") // 指定登录页面 .permitAll() .loginProcessingUrl("/security/do/login.html") // 处理登录请求的地址 .permitAll()
循环重定向问题
去登陆页面和登陆请求本身都需要permitAll()否则登陆和去登陆页面本身都需要登陆,形成死循环.
提交登陆表单
注意:我们以前自己写的登陆表单controller方法以后就不使用了.使用SpringSecurity之后,登陆请求由SpringSecurity处理.
security .authorizeRequests()// 对请求进行授权 .antMatchers("/index.jsp")// 登录页面设置 .permitAll()// 无条件访问 .antMatchers("/static/**") // 对静态资源设置 .permitAll() .antMatchers("/admin/get/page.html") // .hasRole("经理") .access("hasRole('经理') or hasAuthority('user:get')") .anyRequest() //其他任意请求 .authenticated()//认证后访问 .and() .exceptionHandling() //自定义异常映射,以免在filter阶段抛出403异常 .accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { httpServletRequest.setAttribute("exception",new Exception(ConstantUtil.MESSAGE_ACCESS_DENIED)); httpServletRequest.getRequestDispatcher("/WEB-INF/pages/system-error.jsp").forward(httpServletRequest,httpServletResponse); } }) .and() .formLogin() // 开启表单登录功能 .loginPage("/admin/to/login/page.html") // 指定登录页面 .permitAll() .loginProcessingUrl("/security/do/login.html") // 处理登录请求的地址 .permitAll() .usernameParameter("username") // 账号的请求参数名 .passwordParameter("password") // 密码的请求参数名 .defaultSuccessUrl("/admin/to/main/page.html") // 登录成功后跳转的地址 .and() .logout() .logoutUrl("/admin/security/do/logout.html") // 处理退出请求的地址 .logoutSuccessUrl("/admin/to/login/page.html") // 登录退出后跳转的地址 .and() .csrf() // 跨站请求伪造功能 .disable() //取消 ;
登陆操作查询相关数据的SQL
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 1.根据用户名从数据库查询 Admin 对象 AdminExample adminExample = new AdminExample(); adminExample.createCriteria().andLoginAcctEqualTo(username); List<Admin> adminList = adminMapper.selectByExample(adminExample); if (adminList == null || adminList.size() != 1) { return null; } Admin admin = adminList.get(0); // 2.获取数据库中密码 String userpswd = admin.getUserPswd(); // 3.查询 Admin 对应的权限信息(包括角色、权限) Integer adminId = admin.getId(); // 1创建集合用来存放权限信息 Collection<GrantedAuthority> authorities = new ArrayList<>(); // 2根据 adminId 查询对应的角色 List<Role> roleList = roleMapper.selectAssignRole(adminId); for (Role role : roleList) { String roleName = role.getName(); // 注意:一定要加“ROLE_” authorities.add(new SimpleGrantedAuthority("ROLE_" + roleName)); } // 3根据 adminId 查询对应的权限 List<String> authNameList = authMapper.selectAssignedAuthList(adminId); for (String authName : authNameList) { authorities.add(new SimpleGrantedAuthority(authName)); } // 4.封装到 User 的子类 SecurityAdmin 类型的对象中 // User user = new User(username, userpswd, authorities ); SecurityAdmin securityAdmin = new SecurityAdmin(admin, (List<GrantedAuthority>) authorities); // System.out.println(securityAdmin.getPassword()); return securityAdmin; }
<select id="selectAssignedAuthList" resultType="string"> SELECT DISTINCT t_auth.name FROM t_auth LEFT JOIN inner_role_auth ON t_auth.id = inner_role_auth.`auth_id` LEFT JOIN inner_admin_role ON inner_admin_role.`role_id` = inner_role_auth.`role_id` WHERE inner_admin_role.`admin_id` = #{adminId} and t_auth.name !="" and t_auth.name is not null </select>
securityAdmin封装
/** * 考了user对象包含账号和密码, * 为了能够获取原始的admin对象,专门创建这个类对User进行扩展 */ public class SecurityAdmin extends User { private static final long serialVersionUID = 1L; private Admin orignaAdmin; public SecurityAdmin( // 传入原始的admin对象 Admin orignaAdmin, // 创建角色、权限信息的集合 List<GrantedAuthority> authorities){ // 调用父类构造器 super(orignaAdmin.getLoginAcct(),orignaAdmin.getUserPswd(),authorities); this.orignaAdmin=orignaAdmin; // 将原始Admin对象的密码擦除 this.orignaAdmin.setUserPswd(null); } public Admin getOrignaAdmin() { return orignaAdmin; } }
认证功能问题调整
取消手动进行登陆检查的拦截器
springmvc.xml
<!-- 注册拦截器 --> <!--<mvc:interceptors>--> <!--<mvc:interceptor>--> <!--<!– mvc:mapping 配置要拦截的资源 –> <!– /*对应一层路径,比如:/aaa –>--> <!--<!– /**对应多层路径,比如:/aaa/bbb 或/aaa/bbb/ccc 或/aaa/bbb/ccc/ddd –>--> <!--<mvc:mapping path="/**"/>--> <!--<!– mvc:exclude-mapping 配置不拦截的资源 –>--> <!--<mvc:exclude-mapping path="/admin/to/login/page.html"/>--> <!--<mvc:exclude-mapping path="/admin/do/login.html"/>--> <!--<mvc:exclude-mapping path="/admin/do/logout.html"/>--> <!--<!– 配置拦截器类 –>--> <!--<bean class="com.adom.interceptor.LoginInterceptor"/>--> <!--</mvc:interceptor>--> <!--</mvc:interceptors>-->
登陆成功后显示实际登陆用户名
1、导入标签库
<%@ taglib uri="http://www.springframework.org/security/tags" prefix="security" %>
1、使用security:authentication标签
<security:authentication property="principal.orignaAdmin.userName"/>
保存Admin时使用SpringSecuriyt加密方式
@Override public void saveAdmin(Admin admin) { //生成系统当前时间 Date date = new Date(); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String createTime = format.format(date); admin.setCreateTime(createTime); //针对登陆密码进行加密 String source = admin.getUserPswd(); //String encoded = MD5Util.md5(source); String encode = passwordEncoder.encode(source); admin.setUserPswd(encode); //执行保存,如果账号被占用会抛出异常 try { adminMapper.insert(admin); } catch (Exception e) { e.printStackTrace(); //检测当前捕获的异常对象,如果是DuplicateKeyException类型说明账号重复导致 if (e instanceof DuplicateKeyException) { //抛出自定义的LoginAcctAlreadyExist throw new LoginAccountAlreadlyInUse(ConstantUtil.MESSAGE_SYSTEM_ERROR_LOGIN_NOT_UNIQUE); } //如果不是则继续往上抛 throw e; } }
权限控制
controller方法的权限控制
需要进行权限控制的controller方法
java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed