Spring Security框架的简单使用(权限控制,登录,登录加密)
认证:系统提供的用于识别用户身份的功能,通常提供用户名和密码进行登录其实就是在进行认证,认证的目的是让系统知道你是谁。
授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。
常用的权限框架:Spring Security,还有Apache的shiro框架。
Spring Security是 Spring提供的安全认证服务的框架。 使用Spring Security可以帮助我们来简化认证和授权的过程。官网:https://spring.io/projects/spring-security
对应的maven坐标:
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>5.0.5.RELEASE</version> </dependency>
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0"> <display-name>Archetype Created Web Application</display-name> <filter> <!-- 1:DelegatingFilterProxy用于整合第三方框架(代理过滤器,非真正的过滤器,真正的过滤器需要在spring的配置文件) 整合Spring Security时过滤器的名称必须为springSecurityFilterChain, 否则会抛出NoSuchBeanDefinitionException异常 --> <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> <!-- 2:springmvc的核心控制器--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-security.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> </web-app>
SpringSecurityUserService类配合使用
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:security="http://www.springframework.org/schema/security" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <!--1:定义哪些链接可以放行--> <!-- http:用于定义相关权限控制 指定哪些资源不需要进行权限校验,可以使用通配符 --> <security:http security="none" pattern="/css/**"/> <security:http security="none" pattern="/img/**"/> <security:http security="none" pattern="/js/**"/> <security:http security="none" pattern="/aaa.html" /> <security:http security="none" pattern="/login.html"/> <!-- 2:定义哪些链接不可以放行,即需要有角色、权限才可以放行 http:用于定义相关权限控制 auto-config:是否自动配置 设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等 设置为false时需要显示提供登录表单配置,否则会报错 use-expressions:用于指定intercept-url中的access属性是否使用表达式 --> <security:http auto-config="true" use-expressions="true"> <!-- 当前用户访问/**资源 需要有ROLE_ADMIN角色权限 /** :http://localhost:82/a/b/c.html security:intercept-url配置有顺序的 --> <!--只要认证通过就可以访问 isAuthenticated():用户已经认证成功就可以访问此页面 --> <security:intercept-url pattern="/a.html" access="isAuthenticated()" /> <!--拥有add权限就可以访问b.html页面--> <security:intercept-url pattern="/b.html" access="hasAuthority('add')" /> <!--拥有ROLE_ADMIN角色就可以访问c.html页面, 注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_--> <security:intercept-url pattern="/c.html" access="hasRole('ADMIN')" /> <!-- <security:intercept-url pattern="/c.html" access="hasRole('ROLE_ADMIN')" /> --> <!--拥有ROLE_ABC角色就可以访问d.html页面--> <security:intercept-url pattern="/d.html" access="hasRole('ABC')" /> <!-- intercept-url:定义一个拦截规则 pattern:对哪些url进行权限控制 access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表, 请求的用户只需拥有其中的一个角色就能成功访问对应的URL 注意个配置的顺序,如果定义具体访问某个页面的某种权限配置写在这行代码后面,那么后面的具体指定权限会没有用, 所有页面只要通过这行代码的权限就可以访问 --> <security:intercept-url pattern="/**" access="hasRole('isAuthenticated()')"/> <!-- form-login:定义表单登录信息 login-page="/login.html":表示指定登录页面 username-parameter="username":使用登录名的名称,默认值是username password-parameter="password":使用登录名的密码,默认值是password login-processing-url="/login.do":表示登录的url地址 default-target-url="/index.html":登录成功后的url地址 authentication-failure-url="/login.html":认证失败后跳转的url地址,失败后指定/login.html always-use-default-target="true" :不管访问哪个页面被拦截需要登录时,都跳转默认指定的页面,这里默认登录成功(index.html)的页面 如果不配置这个的话,你访问一个不存在的页面,被拦截登录,登录成功后就会直接访问这个不存在的页面,从而报404错误 --> <security:form-login login-page="/login.html" username-parameter="username" password-parameter="password" login-processing-url="/login.do" default-target-url="/index.html" authentication-failure-url="/login.html" always-use-default-target="true" /> <!--登出配置 logout-url="/logout.do" 登出请求 invalidate-session="true" 失效用户信息 logout-success-url="/login.html" 登出成功页面 --> <security:logout logout-url="/logout.do" invalidate-session="true" logout-success-url="/login.html"/> <!-- csrf:对应CsrfFilter过滤器 disabled:是否启用CsrfFilter过滤器,如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403) --> <security:csrf disabled="true"/> </security:http> <!-- 3:认证管理,定义登录账号名和密码,并授予访问的角色、权限 authentication-manager:认证管理器,用于处理认证操作 --> <security:authentication-manager> <!-- authentication-provider:认证提供者,执行具体的认证逻辑 user-service-ref:指定自己写的类,这个类里面会获取数据库查询到的角色权限,然后存入SimpleGrantedAuthority中, 再将impleGrantedAuthority存入User(user不是自己的pojo)中,返回这个user,user会自动匹配密码是否正确以及相应权限 --> <security:authentication-provider user-service-ref="springSecurityUserService"> <!--指定密码加密策略,这个策略需要在spring容器中,这里下文有相应的bean--> <security:password-encoder ref="passwordEncoder"></security:password-encoder> </security:authentication-provider> </security:authentication-manager> <!--扫描包内带@Component注解的类,并将这个类创建到spring核心容器中--> <context:component-scan base-package="com.itheima"/> <!--配置密码加密对象,id是唯一标识,谁要用这个对象就指向这个唯一标识就可以了--> <bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> <!--启用权限注解,只有启动它才能在方法用注解分配权限--> <security:global-method-security pre-post-annotations="enabled"/> </beans>
//加这个注解,这个类才会被创建到spring核心容器,另外这个类必须实现UserDetailsService @Component public class SpringSecurityUserService implements UserDetailsService { //模拟数据库中的用户数据 public static Map<String, User> map = new HashMap<String, User>(); //这里只是为了模拟从数据库取出来密码,所以要用它进行加密操作 public static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); static { com.itheima.pojo.User user1 = new com.itheima.pojo.User(); user1.setUsername("admin"); user1.setPassword("admin2"); com.itheima.pojo.User user2 = new com.itheima.pojo.User(); user2.setUsername("zhangsan"); user2.setPassword("123"); map.put(user1.getUsername(), user1); map.put(user2.getUsername(), user2); } /** * 根据用户名加载用户信息 * * @param username * @return * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { System.out.println("username:" + username); com.itheima.pojo.User userInDb = map.get(username);//模拟根据用户名查询数据库 if (userInDb == null) { //根据用户名没有查询到用户,抛出异常,表示登录名输入有误 return null; } //模拟数据库中的密码,后期需要查询数据库 /*String passwordInDb = "{noop}" + userInDb.getPassword();*/ String passwordInDb = passwordEncoder.encode(userInDb.getPassword()); System.out.println(passwordInDb); List<GrantedAuthority> list = new ArrayList<GrantedAuthority>(); //授权,后期需要改为查询数据库动态获得用户拥有的权限和角色,这里注意,如果添加的是角色,则必须以ROLE_开头,并且全部都是大写, //否则它就会把这个角色当成权限来匹配,如果没有对应的权限的话,你访问不到该角色应该有的功能。原则上加不加没关系,这里只是为了区分角色和权限 list.add(new SimpleGrantedAuthority("add")); // 权限 list.add(new SimpleGrantedAuthority("delete")); // 权限 list.add(new SimpleGrantedAuthority("ROLE_ADMIN")); // 角色 list.add(new SimpleGrantedAuthority("ROLE_ABC")); // 角色 //返回User,参数一:存放登录名,参数二:存放数据库查询的密码(数据库获取的密码,默认会和页面获取的密码进行比对,成功跳转到成功页面,失败回到登录页面,并抛出异常表示失败),存放当前用户具有的角色 UserDetails user = new org.springframework.security.core.userdetails.User(username, passwordInDb, list); return user; } public static void main(String[] args) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String s = encoder.encode("abc"); System.out.println(s); String s1 = encoder.encode("abc"); System.out.println(s1); /*$2a$10$.NZ4a38pAVUE0ahegdnDnOdXJGxm/yhvSUmW9JOktqQmzuIxrNtmK $2a$10$wuj158lZiSdEEBKg8gjUwOmIlWeGu6B0JXrDZ8XycyGP8sSBDk0Yi*/ // 进行判断 boolean b = encoder.matches("abc", "$2a$10$.NZ4a38pAVUE0ahegdnDnOdXJGxm/yhvSUmW9JOktqQmzuIxrNtmK"); System.out.println(b); } }
/** * 权限控制-注解方式(针对controller权限控制) */ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/hello") public class HelloController { @RequestMapping("/add") @PreAuthorize("hasAuthority('delete')")//表示用户必须拥有add权限才能调用当前方法 public String add(){ System.out.println("add..."); return null; } @RequestMapping("/update") @PreAuthorize("hasRole('ROLE_ADMIN')")//表示用户必须拥有ROLE_ADMIN角色才能调用当前方法 public String update(){ System.out.println("update..."); return null; } @RequestMapping("/delete") @PreAuthorize("hasRole('ABC')")//表示用户必须拥有ABC角色才能调用当前方法 public String delete(){ System.out.println("delete..."); return null; } }
SimpleGrantedAuthority和框架里面的org.springframework.security.core.userdetails.User类中,直接返回这个类,就可以自动校验密码,正确访问什么页面,密码错误访问什么页面,正确以后会确定权限,确定权限方式两种,第一种是配置文件中的对页面进行权限控制,里面直接指定了某个页面需要什么权限什么角色才能访问,第二种是在controller类中、方法中添加相应的注解,框架底层会自动匹配相应权限和角色,匹配上则让你处理这个请求,否则就不会。
3、注册的时候会使用到加密,登录不需要加密,因为注册时往数据库中存储的就是密文,只需要把查出来的密文扔进框架就可以了。
4、总的来说,这个框架的功能大体上就是一个分配权限和密码加密,它可以把所有页面请求都拦截,强行让你登录,登录成功就会给你分配权限,你的账号有什么权限就只能做什么操作。
5、密文我也不太懂,大概流程就是,客户端给服务器传过去的就是一串字符串,服务器接收到以后,框架底层自动将字符串加密成密文,并从数据库中取出来的密文中,取出注册时动态加密的盐,然后拼接,并跟数据库中的密文匹配,成功就对了,总之,它绝对安全,除非别人能够获取到数据库中存储的密文,否则绝对解不了。
6、还有一个问题,就是这个框架它默认是不信任嵌套页面,也就是html中的iframe标签,所以如果没有特别设置的话,它就会拦截访问,这里加下面这个配置就好了
<!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问--> <security:headers> <security:frame-options policy="SAMEORIGIN"></security:frame-options> </security:headers>
这个一定要加在security:http标签里面,也就是配置intercept-url:定义一个拦截规则的前面
7、最后一个小逻辑,就是获取当前用户信息返回给前端,因为前端需要它来展示当前是哪个用户在登录,这是通过前端通过判断以后,确定登录成功了,然后发请求,后端框架中就可以直接获取到存放登录信息的对象,返回给前端就可以了
/** * 用户控制层 */ @RestController @RequestMapping("/user") public class UserController { /** * 获取用户名 */ @RequestMapping(value = "/getUserName",method = RequestMethod.GET) public Result getUserName(){ try { User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); String username = user.getUsername(); return new Result(true, MessageConstant.GET_USERNAME_SUCCESS,username); } catch (Exception e) { e.printStackTrace(); return new Result(false, MessageConstant.GET_USERNAME_FAIL); } } }