@wangzhuanyun
2020-09-22T07:29:21.000000Z
字数 7167
阅读 1449
springboot
Spring Security 提供了基于javaEE的企业应有个你软件全面的安全服务。这里特别强调支持使用SPring框架构件的项目,Spring框架是企业软件开发javaEE方案的领导者。如果你还没有使用Spring来开发企业应用程序,我们热忱的鼓励你仔细的看一看。熟悉Spring特别是一来注入原理两帮助你更快更方便的使用Spring Security
正如你可能知道的两个应用程序的两个主要区域是“认证”和“授权”(或者访问控制)。这两个主要区域是Spring Security 的两个目标。“认证”,是建立一个他声明的主题的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统)。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。为了抵达需要授权的店,主体的身份已经有认证过程建立。这个概念是通用的而不只在Spring Security中
认证(验证)就是确认用户的身份,一般采用用户名和密码的形式;授权(鉴权)就是确认用户拥有的身份(角色、权限)能否访问受保护的资源
我们创建一个 HelloController:
@RestControllerpublic class HelloController {@GetMapping("/hello")public String hello() {return "hello";}}
访问 /hello ,需要登录之后才能访问。

默认用户名为user 密码则是项目启动时随机生成的字符串,可以从启动的控制台日志中看到默认密码

这个随机生成的密码,每次启动时都会变。对登录的用户名/密码进行配置,有三种不同的方式:
可以直接在 application.properties 文件中配置用户的基本信息:
spring.security.user.name=userspring.security.user.password=123
配置完成后,重启项目,就可以使用这里配置的用户名/密码登录了。
也可以在 Java 代码中配置用户名密码,首先需要我们创建一个 Spring Security 的配置类,继承自 WebSecurityConfigurerAdapter 类,重写configure方法
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {protected void configure(AuthenticationManagerBuilder auth) throws Exception {//配置用户名 密码 及用户角色(角色目前无用)auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser("wzy").password(passwordEncoder().encode("123")).roles("USER");}@BeanPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}}
从 Spring5 开始,强制要求密码要加密,如果非不想加密,可以使用一个过期的 PasswordEncoder 的实例 NoOpPasswordEncoder,但是不建议这么做,毕竟不安全。
Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同,这样就不需要用户去额外保存盐的字段。
再次登录输入设置的用户名wzy与密码123即可登录。
创建LoginAuthenticationProvider类,实现接口AuthenticationProvider
@Componentpublic class LoginAuthenticationProvider implements AuthenticationProvider {@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {String userName = (String) authentication.getPrincipal(); //拿到usernameString password = (String) authentication.getCredentials(); //拿到passwordSystem.out.println("userName========"+userName);System.out.println("password========"+password);//调用service层 连接数据库 返回登录对象 此处忽略SfAdmin sfAdmin = new SfAdmin();sfAdmin.setAdmin_pwd(password);sfAdmin.setAdmin_name(userName);//判断是否登录 未登录不进入if(sfAdmin!=null){//设置的权限必须以 ROLE开头Collection<GrantedAuthority> authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_ADMIN");return new UsernamePasswordAuthenticationToken(sfAdmin, sfAdmin.getAdmin_pwd(),authorities);}/*验证不通过*/return null;}/*** 如果该AuthenticationProvider支持传入的Authentication对象,则返回true* @param aClass* @return*/@Overridepublic boolean supports(Class<?> aClass) {return aClass.equals(UsernamePasswordAuthenticationToken.class);}}
修改SecurityConfig配置类
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {//实例化过滤器@Autowiredprivate LoginAuthenticationProvider provider;protected void configure(AuthenticationManagerBuilder auth) throws Exception {//登录调用providerauth.authenticationProvider(provider);}@Overrideprotected void configure(HttpSecurity http) throws Exception {http// 关闭csrf防护.csrf().disable().headers().frameOptions().disable().and();http//登录处理.formLogin() //表单方式,或httpBasic.loginPage("/login.html") //自定义登录页面.loginProcessingUrl("/form") //表单提交.defaultSuccessUrl("/admin/hello") //成功登陆后跳转页面.permitAll(); //放行以上路径http.authorizeRequests().antMatchers("/").permitAll() //放行这个请求.antMatchers("/admin/**").hasRole("ADMIN") //admin下的页面需要ADMIN这个角色才可以访问.antMatchers("/user/**").hasRole("USER");}
编写Controller进行测试
@Controller@RequestMapping("/admin")public class IndexController {@RequestMapping("/hello")public String hello(){return "hello";}}@Controller@RequestMapping("/user")public class UserController {@RequestMapping("/hello")public String security(){return "hello";}}
创建类进行登录拦截RbacAuthorityService
@Component("rbacauthorityservice")public class RbacAuthorityService {@Resourceprivate Environment env;@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 返回false时 调用AjaxAuthenticationEntryPoint类* @param request* @param authentication* @return*/public boolean hasPermission(HttpServletRequest request, Authentication authentication) {// 获取当前请求的URIString requestURI = request.getServletPath();// 放开登录urlif (requestURI.equals("/admin/login")){return true;}// 登录判断String token = request.getHeader("userToken");//token需要到redis中找 找不到也是未登录System.out.println(token);if (token == null ){request.setAttribute("flagName","未登录");return false;}// 权限判断 利用token获取用户登录对象 获取角色ID 通过角色ID到redis中找角色对应的权限集合// 利用token去Redis取出当前角色的权限,这里就直接写死了List<String> roles = new ArrayList<>();roles.add("/admin/list");roles.add("/admin/menu");if (!roles.contains(requestURI)){request.setAttribute("flagName","权限不足");return false;}return true;}}
创建AjaxAuthenticationEntryPoint类实现AuthenticationEntryPoint接口,统一返回JSON给前端
/*** 用户没有登录时返回给前端的数据*/@Componentpublic class AjaxAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {String msgName = httpServletRequest.getAttribute("msgName").toString();MassageJson massageJson = new MassageJson();if (msgName.equals("未登录")){massageJson.setCode(001);massageJson.setMsg("未登录");} else if (msgName.equals("权限不足")){massageJson.setCode(002);massageJson.setMsg("权限不足");} else{massageJson.setCode(003);massageJson.setMsg("系统异常");}httpServletResponse.setCharacterEncoding("utf-8");httpServletResponse.getWriter().write(JSON.toJSONString(massageJson));}}
修改配置类SecurityConfig
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 未登陆时返回 JSON 格式的数据给前端*/@ResourceAjaxAuthenticationEntryPoint ajaxAuthenticationEntryPoint;@Overrideprotected void configure(HttpSecurity http) throws Exception {// 去掉 CSRFhttp.csrf().disable().exceptionHandling().authenticationEntryPoint(ajaxAuthenticationEntryPoint).and()// 基于Token 不需要Session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 登录处理.and().formLogin().loginProcessingUrl("admin/login").permitAll()// 登录和权限控制.and().authorizeRequests().anyRequest()// RBAC 动态 url 认证.access("@rbacauthorityservice.hasPermission(request,authentication)");}}
创建AjaxLogoutSuccessHandler类实现接口LogoutSuccessHandler,进行注销
/*** 退出登录*/@Componentpublic class AjaxLogoutSuccessHandler implements LogoutSuccessHandler {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {// 从这里拿到token 然后把这个token注销String token = httpServletRequest.getHeader("userToken");// 去redis删除tokenstringRedisTemplate.delete(token);MassageJson massageJson = new MassageJson();massageJson.setCode(004);massageJson.setMsg("退出成功!");httpServletResponse.setContentType("text/html;charset=utf-8");httpServletResponse.getWriter().write(JSON.toJSONString(massageJson));}}
修改SecurityConfig配置类
/*** 注销成功返回的 JSON 格式数据给前端*/@ResourceAjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;//在configure方法下添加 注销处理.and().logout()//默认注销行为为logout.logoutUrl("/admin/loginOut") //设置调用路径.logoutSuccessHandler(ajaxLogoutSuccessHandler).permitAll();