Spring Security 初探

现在很多企业和开发团队都使用了SSH2(Struts 2 +Spring 2.5 +Hibernate)框架来进行开发,  我们或许已经习惯了强大的Spring Framework 全局配置管理,不可否认,Sping是一个很优秀的开源框架,但是由于Spring3.0版本后强大的的注解式bean的诞生,Spring MVC框架这匹黑马正悄然杀起,但今天Spring MVC不是主角,今天我和大家分享一个同样隶属于SpringSource 的安全框架——Spring Security, 下面的基于Spring MVC给大家分享一下Spring Security  的使用。虽然对它的接触时间不长,参考了一些网上朋友的做法,但也按照我的理解把这个框架介绍介绍,不是很专业,还请大家不要介意 。

我们知道,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个资源来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

首先,我们看web.xml

Java代码  

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
  4. http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  5. <!-- 编码统一最好放最上面,最先加载,防止乱码-->
  6. <filter>
  7. <filter-name>Set Character Encoding</filter-name>
  8. <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  9. <init-param>
  10. <param-name>encoding</param-name>
  11. <param-value>UTF-8</param-value>
  12. </init-param>
  13. <init-param>
  14. <param-name>forceEncoding</param-name>
  15. <param-value>true</param-value><!-- 强制进行转码 -->
  16. </init-param>
  17. </filter>
  18. <filter-mapping>
  19. <filter-name>Set Character Encoding</filter-name>
  20. <url-pattern>/*</url-pattern>
  21. </filter-mapping>
  22. <!-- 然后接着是SpringSecurity必须的filter 优先配置,让SpringSecurity先加载,防止SpringSecurity拦截失效-->
  23. <filter>
  24. <filter-name>springSecurityFilterChain</filter-name>
  25. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  26. </filter>
  27. <filter-mapping>
  28. <filter-name>springSecurityFilterChain</filter-name>
  29. <url-pattern>/*</url-pattern>
  30. </filter-mapping>
  31. <welcome-file-list>
  32. <welcome-file>index.jsp</welcome-file>
  33. </welcome-file-list>
  34. <!--
  35. spring需要加载的配置文件
  36. -->
  37. <context-param>
  38. <param-name>contextConfigLocation</param-name>
  39. <param-value>
  40. WEB-INF/classes/applicationContext.xml,
  41. WEB-INF/spring3-servlet.xml,
  42. WEB-INF/spring-security.xml
  43. </param-value>
  44. </context-param>
  45. <listener>
  46. <listener-class>
  47. <!--     所以,要在web.xml下面配置好监听,让服务器启动时就初始化改类,可以得到request   -->
  48. org.springframework.web.context.request.RequestContextListener
  49. </listener-class>
  50. </listener>
  51. <listener>
  52. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  53. </listener>
  54. <!--
  55. 默认所对应的配置文件是WEB-INF下的{servlet-name}-servlet.xml,这里便是:spring3-servlet.xml
  56. -->
  57. <servlet>
  58. <servlet-name>spring3</servlet-name>
  59. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  60. <load-on-startup>1</load-on-startup>
  61. </servlet>
  62. <servlet-mapping>
  63. <servlet-name>spring3</servlet-name>
  64. <!--
  65. 这里可以用 / 但不能用 /*
  66. ,拦截了所有请求会导致静态资源无法访问,所以要在spring3-servlet.xml中配置mvc:resources
  67. -->
  68. <url-pattern>/</url-pattern>
  69. </servlet-mapping>
  70. </web-app>

注释已经写了挺多,还是稍微解释一下要注意的地方,一个是UTF-8编码转换,这个最好加在最前面,让它先生效,我在调试的时候就出过这种情况,web.xml里的其他配置都正常生效了,但是编码死活不行,一中文就乱码,郁闷了老半天,然后突发奇想,是不是web.xml里先声明的配置先生效,后声明的后生效?接着实践,果然不出我所料,把编码转换加在前面,一切正常。。。。我那个晕。。。

关于Spirng MVC的就不说了,那些数据访问、业务和控制层那些的东东就自个研究去了吧。。这不是今天的重点。

接着是spring-security.xml

Java代码  

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:security="http://www.springframework.org/schema/security"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  6. http://www.springframework.org/schema/security
  7. http://www.springframework.org/schema/security/spring-security-3.0.xsd">
  8. <!--  Spring-Security 的配置 -->
  9. <!--
  10. 注意use-expressions=true.表示开启表达式,否则表达式将不可用. see:http://www.family168.com/tutorial/springsecurity3/html/el-access.html
  11. -->
  12. <security:http auto-config="true" use-expressions="false" access-denied-page="/user/login_failure.html">
  13. <!--允许所有人访问-->
  14. <!--     <security:intercept-url pattern="/**" access="permitAll" />-->
  15. <!--允许ROLE_ADMIN权限访问-->
  16. <security:intercept-url pattern="/user/findAll.html" access="ROLE_ADMIN" />
  17. <!--允许ROLE_ADMIN权限访问-->
  18. <security:intercept-url pattern="/user/**" access="ROLE_ADMIN" />
  19. <!--允许ROLE_USER权限访问-->
  20. <security:intercept-url pattern="/success.jsp" access="ROLE_USER,ROLE_ADMIN" />
  21. <!--允许IS_AUTHENTICATED_ANONYMOUSLY匿名访问-->
  22. <security:intercept-url pattern="/anonymously.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />
  23. <!-- filters="none"  不过滤这些资源-->
  24. <security:intercept-url pattern="/js/**" filters="none" />
  25. <security:intercept-url pattern="/index.jsp" filters="none" />
  26. <!-- login-page:默认指定的登录页面. authentication-failure-url:出错后跳转页面. default-target-url:成功登陆后跳转页面 -->
  27. <security:form-login login-page="/index.jsp" authentication-failure-url="/user/login_failure.html"
  28. default-target-url="/success.jsp" />
  29. <!--
  30. invalidate-session:指定在退出系统时是否要销毁Session。logout-success-url:退出系统后转向的URL。logout-url:指定了用于响应退出系统请求的URL。其默认值为:/j_spring_security_logout。
  31. -->
  32. <security:logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/j_spring_security_logout" />
  33. <!--
  34. max-sessions:允许用户帐号登录的次数。范例限制用户只能登录一次。exception-if-maximum-exceeded:
  35. 默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空。当exception-if-maximum-exceeded="true"时系统会拒绝第二次登录。
  36. -->
  37. <security:session-management>
  38. <security:concurrency-control error-if-maximum-exceeded="true" max-sessions="1" />
  39. </security:session-management>
  40. </security:http>
  41. <!-- 指定一个自定义的authentication-manager :customUserDetailsService -->
  42. <security:authentication-manager>
  43. <security:authentication-provider user-service-ref="customUserDetailsService">
  44. <security:password-encoder ref="passwordEncoder" />
  45. </security:authentication-provider>
  46. </security:authentication-manager>
  47. <!-- 对密码进行MD5编码 -->
  48. <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
  49. <!--
  50. 通过 customUserDetailsService,Spring会控制用户的访问级别.
  51. 也可以理解成:以后我们和数据库操作就是通过customUserDetailsService来进行关联.
  52. -->
  53. <bean id="customUserDetailsService" class="org.yzsoft.springmvcdemo.util.CustomUserDetailsService" />
  54. <!-- 自定义登陆错误提示,可以取出mymessages.properties的国际化消息-->
  55. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
  56. <property name="basename" value="classpath:org/yzsoft/springmvcdemo/mymessages" />
  57. </bean>
  58. <bean id="localeResolver" class="org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver" />
  59. </beans>

这个多解释一下,首先

Java代码  

  1. <security:intercept-url pattern="/findAll.html" access="hasRole(‘ROLE_ADMIN‘)" />
  2. <security:intercept-url pattern="/user/**" access="hasRole(‘ROLE_ADMIN‘)" />
  3. <security:intercept-url pattern="/anonymously.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" />

这个是权限控制,声明了拥有什么权限可以访问哪些资源,这个配置的是有ROLE_ADMIN权限的才可以访问/findAll.html,至于这个ROLE_ADMIN从哪来,呆会再解释。或者像第二句一样配置也可以:拥有ROLE_ADMIN权限的才可以访问/user/下的所有资源,否则都会抛出AccessDeniedException 。

IS_AUTHENTICATED_ANONYMOUSLY就是匿名访问的意思,这个相信都懂的。。。

然后是登陆和安全退出

Java代码  

  1. <security:form-login login-page="/index.jsp" authentication-failure-url="/user/login_failure.html"default-target-url="/user/findAll.html" />
  2. <security:logout invalidate-session="true" logout-success-url="/index.jsp" logout-url="/user/login_failure.html" />

解释下上面一句,相信看也能看出来了的,login-page:默认指定的登录页面. authentication-failure-url:出错后跳转页面(包括那些个啥用户名密码错误吖。。啥啥啥的。). default-target-url:成功登陆后跳转页面 (这里我直接跳到的控制器去查数据列表)。

接着:invalidate-session:指定在退出系统时是否要销毁Session。logout-success-url:退出系统后转向的URL。logout-url:指定了用于响应退出系统请求的URL。其默认值为:/j_spring_security_logout。

接下来是一个比较不错的功能:是否允许同一用户多处登陆

Java代码  

  1. <security:session-management>
  2. <security:concurrency-control error-if-maximum-exceeded="true" max-sessions="1" />
  3. </security:session-management>

exception-if-maximum-exceeded:
   默认为false,此值表示:用户第二次登录时,前一次的登录信息都被清空。当error-if-maximum-exceeded="true"时系统会拒绝第二次登录。

max-sessions:允许用户帐号登录的次数,这里我们允许一次登陆。这里我们做个实验吧,看看是不是真的生效了,请看图

这里我们看到,当同一个账号多处登陆时,就会报出Maximum sessions of 1 for this principal exceeded 的错误,当然,正式使用我们换成mymessages.properties里的我们自定义的国际化消息,这样人性化一点。

好,我们往下看,接着就是应用我们实际项目里的自定义用户权限了

Java代码  

  1. <security:authentication-manager>
  2. <security:authentication-provider user-service-ref="customUserDetailsService">
  3. <security:password-encoder ref="passwordEncoder" />
  4. </security:authentication-provider>
  5. </security:authentication-manager>
  6. <!-- 对密码进行MD5编码 -->
  7. <bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" />
  8. <bean id="customUserDetailsService" class="org.yzsoft.springmvcdemo.util.CustomUserDetailsService" />

首先是<security:authentication-manager>是指定我们自定义的身份验证策略,这里我们用customUserDetailsService这个bean,就是指向我们CustomUserDetailsService.java这个类。然后<security:password-encoder>指定我们密码使用MD5进行编码,调用Spring Security自带的MD5加密类。当然,还有加盐MD5或我们自己写的加密算法等安全性更加高的密码策略。这个按项目实际使用配置吧。

然后看到我们的CustomUserDetailsService.java

Java代码  

  1. package org.yzsoft.springmvcdemo.util;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. import java.util.List;
  5. import org.apache.log4j.Logger;
  6. import org.springframework.beans.factory.annotation.Autowired;
  7. import org.springframework.dao.DataAccessException;
  8. import org.springframework.security.core.GrantedAuthority;
  9. import org.springframework.security.core.authority.GrantedAuthorityImpl;
  10. import org.springframework.security.core.userdetails.User;
  11. import org.springframework.security.core.userdetails.UserDetails;
  12. import org.springframework.security.core.userdetails.UserDetailsService;
  13. import org.springframework.security.core.userdetails.UsernameNotFoundException;
  14. import org.yzsoft.springmvcdemo.serviceimpl.UsersServiceImpl;
  15. import org.yzsoft.springmvcdemo.vo.TUsers;
  16. /**
  17. * 一个自定义的类用来和数据库进行操作. 即以后我们要通过数据库保存权限.则需要我们继承UserDetailsService
  18. *
  19. * @author
  20. *
  21. */
  22. public class CustomUserDetailsService implements UserDetailsService {
  23. protected static Logger logger = Logger.getLogger("service");//log4j,不用解释了吧。。
  24. @Autowired
  25. private UsersServiceImpl usersService;
  26. public UsersServiceImpl getUsersService() {
  27. return usersService;
  28. }
  29. public void setUsersService(UsersServiceImpl usersService) {
  30. this.usersService = usersService;
  31. }
  32. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
  33. UserDetails user = null;
  34. try {
  35. // 搜索数据库以匹配用户登录名.
  36. // 我们可以通过dao使用Hibernate来访问数据库
  37. System.out.println(username + "   用户页面输入的用户名");
  38. TUsers tusers = this.usersService.findByUsername(username);
  39. System.out.println(tusers.getUsername() + "   数据库取出的用户名");
  40. // Populate the Spring User object with details from the dbUser
  41. // Here we just pass the username, password, and access level
  42. // getAuthorities() will translate the access level to the correct
  43. // role type
  44. // 用户名、密码、是否启用、是否被锁定、是否过期、权限
  45. user = new User(tusers.getUsername(), tusers.getPassword().toLowerCase(), true, true, true, true, getAuthorities(Integer.parseInt(tusers.getRole())));
  46. } catch (Exception e) {
  47. logger.error("用户信息错误!");
  48. throw new UsernameNotFoundException("异常处理:检索用户信息未通过!");
  49. }
  50. return user;
  51. }
  52. /**
  53. * 获得访问角色权限列表
  54. *
  55. * @param access
  56. * @return
  57. */
  58. public Collection<GrantedAuthority> getAuthorities(Integer role) {
  59. System.out.println("取得的权限是  :" + role);
  60. List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
  61. // 所有的用户默认拥有ROLE_USER权限
  62. if (role == 0) {
  63. System.out.println("普通用户");
  64. logger.debug("取得普通用户权限-->");
  65. authList.add(new GrantedAuthorityImpl("ROLE_USERS"));
  66. }
  67. // 如果参数role为1.则拥有ROLE_ADMIN权限
  68. if (role == 1) {
  69. logger.debug("取得ADMIN用户权限-->");
  70. authList.add(new GrantedAuthorityImpl("ROLE_ADMIN"));
  71. }
  72. System.out.println(authList.size()+"  权限列表长度");
  73. return authList;
  74. }
  75. }

这里就是把我们从数据库里面取得的用户权限和Spring Security的配置进行桥接,还记得上面配置文件里的ROLE_ADMIN吧,就是从这里来的,很奇怪的是,这个必须设置成ROLE_ 开头才有效。。郁闷。。。

这里我刚开始有一个疑惑,我们看这2句

Java代码  

  1. TUsers tusers = this.usersService.findByUsername(username);
  2. user = new User(tusers.getUsername(), tusers.getPassword().toLowerCase(), true, true, true, true, getAuthorities(Integer.parseInt(tusers.getRole())));

这里根本不需要用户输入的密码,只要了用户名,然后直接根据用户名去取权限,就直接设置进Spring Security的User对象里面去,我不禁一身冷汗,这不相当于说有了用户名就直接去查数据库么,而且是不用密码的。。。。

但经过查看官方文档和网上的解释,这才放心,原来是这样的,Spring Security的确是直接根据用户名去查,但是查得出来的Spring Security  User对象之后,它会根据这个对象的属性值去数据库查询与这个对象匹配的数据,我们这里设置的是(用户名,密码,是否启用、是否被锁定、是否过期、权限。。。),那么如果数据库存在这个对象,就返回真,否则返回假,这样也就不用担心了,完全可靠。就是我们在前台要做好限制,不能给用户不输密码就访问, 不然挤爆你数据库连接。。。。。

最后登陆页面index.jsp

Java代码  

  1. <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
  2. <%
  3. String path = request.getContextPath();
  4. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
  5. %>
  6. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  7. <html>
  8. <head>
  9. <base href="<%=basePath%>">
  10. <title>My JSP ‘index.jsp‘ starting page</title>
  11. <meta http-equiv="pragma" content="no-cache">
  12. <meta http-equiv="cache-control" content="no-cache">
  13. <meta http-equiv="expires" content="0">
  14. <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
  15. <meta http-equiv="description" content="This is my page">
  16. </head>
  17. <body>
  18. 用户登陆 <br>
  19. ${SPRING_SECURITY_LAST_EXCEPTION.message}
  20. <form action="j_spring_security_check" method="post">
  21. USERNAME:<input type="text" name="j_username" value="${sessionScope[‘SPRING_SECURITY_LAST_USERNAME‘]}" /><br/>
  22. PASSWORD:<input type="password" name="j_password" value="" /><br/>
  23. <input type="checkbox" name="_spring_security_remember_me" />两周之内不必登陆(这个功能没有做的)<br/>
  24. <input type="submit">
  25. </form>
  26. </body>
  27. </html>

这里我还是使用Spring Security默认的j_username和j_password,表单目标也用默认的j_spring_security_check,会默认跳到Spring Security进行拦截。其他的应该不用解释了吧。。。。

最后 ,我们整理一下Spring Security的整个控制过程:

——>用户登陆

——> <security:authentication-manager> 拦截

——>交给customUserDetailsService处理,并且声明密码采用MD5策略

——>根据输入的用户名去数据库查这条记录,验证身份

——>取出该条记录的用户权限(锁定、禁用、过期和实际权限等)

——>根据取得的权限列表去security:intercept-url匹配、授权,然后判断是否放行。

这就完成了一整个的权限控制流程。

接下来我们来测试一下看是否真的生效了:

1、测试匿名访问页面,直接地址栏访问:

2、普通用户登陆

然后访问后台管理页面,跳回了登陆页

3、管理员用户登陆

退出后成功跳转回登陆页,点击后退再执行其他操作,这时候session已经注销了的,不能执行,又跳回了登陆页。是我们想要的效果,OK,成功了。

好了,Spring Security的简单使用就讲到这里,其实这只是Spring Security的一小部分,而且这里我还没有用权限表对用户权限进行专门的管理,很多东西还是用Spring Security 默认的,还有Spring Security CAS (单点登陆)以及更加高级的权限控制和更完善的Spring Security 配置,以后我们再慢慢去研究吧。发现Spring Security 这个技术不仅简化了我们的用户权限管理,要知道我们做管理系统的时候这是个大问题,也差不多颠覆了我一贯以来用户权限管理的观念,但是掌握了这种思维之后,又发现,其实,程序并不是只有一种实现方式,它激发了我写程序时要去寻找多种解决方案的想法。学习的路上,就是要不断推翻自己固有的思维,去见识多种新事物,才能有进步

文章摘自:http://yzxqml.iteye.com/blog/1756106

时间: 2024-08-25 23:55:41

Spring Security 初探的相关文章

Spring Security基本原理

近期研究了Spring Security,现进行记录. 首先先进行一个最简单的demo.默认情况下,在Spring Boot里,如果在classpath下面有Spring Security相关的jar包,那么Spring Boot会自动地替我们做一些安全的配置.本Demo正是基于Spring Boot构建,使用maven进行项目管理.maven核心POM依赖配置如下: 1234567891011121314151617181920212223242526272829303132333435363

【转】Spring Security Authentication (认证)

Spring Security Authentication (认证)原理初探 Spring Security Authentication (认证)定制开发 原文地址:https://www.cnblogs.com/qiyebao/p/12218743.html

Spring Security入门Demo

一.spring Security简介 SpringSecurity,这是一种基于Spring AOP和Servlet过滤器的安全框架.它提供全面的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权.在Spring Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency Injection)和面向切面技术. 二.建立工程 参考http://blog.csdn.net/haishu_zheng/article/details/51490

CAS 与 Spring Security 3整合配置详解

一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分.用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统.用户授权指的是验证某个用户是否有权限执行某个操作.在一个系统中,不同用户所具有的权限是不同的.比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改.一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限. 对于上面提到的两种应用情景,Spring Security 框

spring security+mybatis+springMVC构建一个简单的项目

1.引用 spring security ,这是一种基于spring AOP和Servlet的过滤安全框架.它提供全面的安全性解决方案,同时在web请求级和方法的调用级处理身份确认和授权.在spring framework基础上,spring security充分利用了依赖注入(DI,Dependency Injection)和AOP技术. 下面就让我们用一个小的晓得项目来出初步了解Spring Security 的强大功能吧. 2.项目实战    1)项目的技术架构:maven+spring

Spring Security视频地址

1:Spring Security视频 附件为txt文档内含百度云盘的链接,由于视频太大,所以只能分享链接了..... http://pan.baidu.com/share/link?shareid=2726555995&uk=706734182  提取码:60tb 2:Spring Securoty: 链接:http://pan.baidu.com/s/1o6x2sye 密码:fi2x 3:链接: http://pan.baidu.com/s/1pJnylQF 密码: wj6b 4:http:

spring security oauth2 jwt 认证和资源分离的配置文件(java类配置版)

最近再学习spring security oauth2.下载了官方的例子sparklr2和tonr2进行学习.但是例子里包含的东西太多,不知道最简单最主要的配置有哪些.所以决定自己尝试搭建简单版本的例子.学习的过程中搭建了认证和资源在一个工程的例子,将token存储在数据库的例子等等 .最后做了这个认证和资源分离的jwt tokens版本.网上找了一些可用的代码然后做了一个整理, 同时测试了哪些代码是必须的.可能仍有一些不必要的代码在,欢迎大家赐教. 一.创建三个spring boot 工程,分

springsecurity启动出现org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: You must use a 3.0 schema with Spring Security 3.0.

在换了spring-security的jar包以后启动出现org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: You must use a 3.0 schema with Spring Security 3.0.Please update your schema declarations to the 3.0.3 schema (spring-securi

REST Security with JWT using Java and Spring Security

Security Security is the enemy of convenience, and vice versa. This statement is true for any system, virtual or real, from the physical house entrance to web banking platforms. Engineers are constantly trying to find the right balance for the given